@coinbase/coinbase-cli 0.0.0 → 0.0.1
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/LICENSE +21 -0
- package/README.md +190 -0
- package/dist/index.js +253 -0
- package/dist/version.txt +1 -0
- package/package.json +42 -5
- package/scripts/postinstall.mjs +51 -0
- package/skills/coinbase/SKILL.md +177 -0
- package/index.js +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Coinbase
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# \@coinbase/coinbase-cli
|
|
2
|
+
|
|
3
|
+
Coinbase for agents — CLI and MCP server for the [Coinbase platform](https://docs.cdp.coinbase.com). Authenticate once, access every API from your terminal or AI agent.
|
|
4
|
+
|
|
5
|
+
Zero runtime dependencies — just Node.js 22+.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @coinbase/coinbase-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# 1. Set up your API key (get one at coinbase.com/settings/api)
|
|
17
|
+
coinbase env live --key-id <id> --key-secret <secret>
|
|
18
|
+
|
|
19
|
+
# 2. Make a request
|
|
20
|
+
coinbase products list
|
|
21
|
+
|
|
22
|
+
# 3. Explore available commands
|
|
23
|
+
coinbase --help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Commands
|
|
27
|
+
|
|
28
|
+
### `coinbase env` — Manage API keys
|
|
29
|
+
|
|
30
|
+
Configure API keys once; all subsequent commands authenticate automatically.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
coinbase env # list environments
|
|
34
|
+
coinbase env live --key-id <id> --key-secret <secret> # configure inline
|
|
35
|
+
coinbase env live --remove # remove an environment
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Multiple API keys — prefix `live` to inherit its URL with a different key:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
coinbase env live-team-a --key-id <id> --key-secret <secret> # inherits live URL
|
|
42
|
+
coinbase env live # switch back to live
|
|
43
|
+
coinbase products list -e live-team-a # one-off override
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Custom environment names that don't start with `live` require `--url`:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
coinbase env custom --key-id <id> --key-secret <secret> --url https://api.coinbase.com
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### `coinbase <resource> <action>` — API operations
|
|
53
|
+
|
|
54
|
+
Commands are generated from the API spec. Run `coinbase --help` to see available resources and actions.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
coinbase products list
|
|
58
|
+
coinbase products get product_id=BTC-USD
|
|
59
|
+
coinbase orders list
|
|
60
|
+
coinbase orders create product_id=BTC-USD side=BUY ...
|
|
61
|
+
coinbase balance
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Use `--template` to see the expected request shape before writing, and `--dry-run` to preview without sending:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
coinbase orders create --template # print request body template
|
|
68
|
+
coinbase orders create --dry-run # preview the assembled request
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Field syntax:**
|
|
72
|
+
|
|
73
|
+
| Syntax | Meaning |
|
|
74
|
+
|---|---|
|
|
75
|
+
| `key=value` | String body field |
|
|
76
|
+
| `key:=value` | Raw JSON body field |
|
|
77
|
+
| `key==value` | Query parameter |
|
|
78
|
+
| `a.b.c=value` | Nested body field |
|
|
79
|
+
| `@file.json` | Body from file |
|
|
80
|
+
| `-` | Body from stdin |
|
|
81
|
+
| `Header:value` | Custom HTTP header |
|
|
82
|
+
|
|
83
|
+
**Resource flags:**
|
|
84
|
+
|
|
85
|
+
| Flag | Behavior |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `--template` | Print request body template as JSON |
|
|
88
|
+
| `--dry-run` | Print request without sending |
|
|
89
|
+
| `--edit` | Open request body in `$EDITOR` |
|
|
90
|
+
| `--jq <expr>` | Filter JSON response (requires [jq](https://jqlang.github.io/jq/)) |
|
|
91
|
+
| `--paginate` | Auto-follow pagination for full results |
|
|
92
|
+
| `-e <env>` | Use a specific environment |
|
|
93
|
+
|
|
94
|
+
### `coinbase history` — Request history
|
|
95
|
+
|
|
96
|
+
View and manage a log of recent API requests made through the CLI.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
coinbase history # show recent requests
|
|
100
|
+
coinbase history clear # clear request history
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `coinbase skills` — Workflow skills
|
|
104
|
+
|
|
105
|
+
Manage reusable workflow skills (prompt templates) that extend the MCP server.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
coinbase skills list # list available skills
|
|
109
|
+
coinbase skills add # install/update all bundled skills
|
|
110
|
+
coinbase skills add --dir <path> # install to a specific directory
|
|
111
|
+
coinbase skills remove # remove all installed skills
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Claude Code / MCP
|
|
115
|
+
|
|
116
|
+
`coinbase mcp` starts an [MCP](https://modelcontextprotocol.io) server that gives AI agents typed tool access to every Coinbase API endpoint. Tools are generated from the spec — new endpoints appear automatically when the CLI updates.
|
|
117
|
+
|
|
118
|
+
In addition to API tools, the MCP server exposes meta-tools for environment and workflow management:
|
|
119
|
+
|
|
120
|
+
| Tool | Purpose |
|
|
121
|
+
|---|---|
|
|
122
|
+
| `coinbase_set_env` | Switch the active API environment |
|
|
123
|
+
| `coinbase_env` | Show current environment and key info |
|
|
124
|
+
| `coinbase_help` | Get usage help for CLI commands |
|
|
125
|
+
|
|
126
|
+
**If you installed the CLI globally:**
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
claude mcp add --scope user --transport stdio coinbase -- coinbase mcp
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**MCP only** (no global install needed — runs via `npx`):
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
claude mcp add --scope user --transport stdio coinbase -- npx -y @coinbase/coinbase-cli mcp
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> If you use both the CLI and MCP, install globally so they share the same version and spec.
|
|
139
|
+
|
|
140
|
+
Or add to your project's `.mcp.json`:
|
|
141
|
+
|
|
142
|
+
```json
|
|
143
|
+
{
|
|
144
|
+
"mcpServers": {
|
|
145
|
+
"coinbase": { "transport": "stdio", "command": "coinbase", "args": ["mcp"] }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Auto-approve read operations** in `.claude/settings.json`:
|
|
151
|
+
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"permissions": {
|
|
155
|
+
"allow": [
|
|
156
|
+
"mcp__coinbase__coinbase_env",
|
|
157
|
+
"mcp__coinbase__coinbase_help",
|
|
158
|
+
"mcp__coinbase__coinbase_*_list",
|
|
159
|
+
"mcp__coinbase__coinbase_*_get"
|
|
160
|
+
]
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Configuration
|
|
166
|
+
|
|
167
|
+
Environment variables override config file values:
|
|
168
|
+
|
|
169
|
+
| Variable | Purpose |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `COINBASE_ENV` | Active environment name |
|
|
172
|
+
| `COINBASE_KEY_ID` | API key ID |
|
|
173
|
+
| `COINBASE_KEY_SECRET` | API key secret |
|
|
174
|
+
| `COINBASE_URL` | Base URL override |
|
|
175
|
+
| `COINBASE_NO_HISTORY` | Set to `1` to disable request history |
|
|
176
|
+
| `COINBASE_CONFIG_DIR` | Config directory (default: `~/.config/coinbase`, `%APPDATA%\coinbase` on Windows) |
|
|
177
|
+
|
|
178
|
+
Keys and state are stored in `config.json` within the config directory.
|
|
179
|
+
|
|
180
|
+
## Contributing
|
|
181
|
+
|
|
182
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
183
|
+
|
|
184
|
+
## Security
|
|
185
|
+
|
|
186
|
+
See [SECURITY.md](SECURITY.md).
|
|
187
|
+
|
|
188
|
+
## License
|
|
189
|
+
|
|
190
|
+
[MIT](LICENSE)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var hn=Object.defineProperty;var Pn=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var C=(t,e)=>()=>(t&&(e=t(t=0)),e);var Ht=(t,e)=>{for(var r in e)hn(t,r,{get:e[r],enumerable:!0})};var m,u=C(()=>{m={live:"https://api.coinbase.com"}});var y,g=C(()=>{y={production:"live"}});var Pe,Rt,ke,Re,vt=C(()=>{"use strict";u();g();Pe="@coinbase/coinbase-cli",Rt="https://registry.npmjs.org",ke=typeof m<"u"?m:{live:"https://api.coinbase.com"},Re=typeof y<"u"?y:{production:"live"}});function et(t){return jt+Buffer.from(t,"utf8").toString("base64")}function rt(t){return t.startsWith(jt)?Buffer.from(t.slice(jt.length),"base64").toString("utf8"):t}var jt,Tt=C(()=>{"use strict";u();g();jt="b64:"});import{execFileSync as At}from"node:child_process";var xt,St,ve=C(()=>{"use strict";u();g();Tt();xt="/usr/bin/security",St=class{constructor(){try{At(xt,["list-keychains"],{stdio:["pipe","pipe","pipe"]})}catch{throw new Error("Keychain not accessible")}}get(e,r){try{let n=At(xt,["find-generic-password","-s",e,"-a",r,"-w"],{encoding:"utf8",stdio:["pipe","pipe","pipe"]}).trimEnd();return rt(n)}catch{return null}}set(e,r,n){if(/[\x00-\x1f\x7f]/.test(e)||/[\x00-\x1f\x7f]/.test(r))throw new Error("keychain: control characters in service or account name");let i=o=>`"${o.replace(/(["\\])/g,"\\$1")}"`,s=`add-generic-password -U -s ${i(e)} -a ${i(r)} -w ${et(n)}
|
|
3
|
+
`;if(At(xt,["-i"],{input:s,stdio:["pipe","pipe","pipe"]}),this.get(e,r)!==n)throw new Error("Keychain write failed: verify-after-write mismatch")}delete(e,r){try{At(xt,["delete-generic-password","-s",e,"-a",r],{stdio:["pipe","pipe","pipe"]})}catch{}}}});import{execFileSync as Ct}from"node:child_process";var wt,Te=C(()=>{"use strict";u();g();Tt();wt=class{constructor(){try{Ct("which",["secret-tool"],{stdio:["pipe","pipe","pipe"]})}catch{throw new Error("secret-tool not found")}}get(e,r){try{let n=Ct("secret-tool",["lookup","service",e,"account",r],{encoding:"utf8",stdio:["pipe","pipe","pipe"]});return rt(n.trimEnd())}catch{return null}}set(e,r,n){Ct("secret-tool",["store","--label",`${e}:${r}`,"service",e,"account",r],{input:et(n),stdio:["pipe","pipe","pipe"]})}delete(e,r){try{Ct("secret-tool",["clear","service",e,"account",r],{stdio:["pipe","pipe","pipe"]})}catch{}}}});import{execFileSync as vn}from"node:child_process";function Kt(t,e){return`${t}:${e}`}function Nt(t){return t.replace(/'/g,"''")}function Yt(t,e){let r=Tn+t,n=Buffer.from(r,"utf16le").toString("base64"),i=e===void 0?process.env:{...process.env,[Ae]:e};return vn("powershell.exe",["-NoProfile","-NonInteractive","-EncodedCommand",n],{encoding:"utf8",stdio:["pipe","pipe","pipe"],env:i}).trimEnd()}var Tn,Ae,Gt,xe=C(()=>{"use strict";u();g();Tt();Tn=`
|
|
4
|
+
if (-not ([System.Management.Automation.PSTypeName]'Win32.Cred').Type) {
|
|
5
|
+
Add-Type -Namespace Win32 -Name Cred -MemberDefinition '
|
|
6
|
+
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
7
|
+
public static extern bool CredWrite(ref CREDENTIAL cred, int flags);
|
|
8
|
+
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
9
|
+
public static extern bool CredRead(string target, int type, int flags, out IntPtr cred);
|
|
10
|
+
[DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
|
|
11
|
+
public static extern bool CredDelete(string target, int type, int flags);
|
|
12
|
+
[DllImport("advapi32.dll")] public static extern void CredFree(IntPtr cred);
|
|
13
|
+
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
|
|
14
|
+
public struct CREDENTIAL {
|
|
15
|
+
public int Flags; public int Type; public string TargetName; public string Comment;
|
|
16
|
+
public long LastWritten; public int CredentialBlobSize; public IntPtr CredentialBlob;
|
|
17
|
+
public int Persist; public int AttributeCount; public IntPtr Attributes;
|
|
18
|
+
public string TargetAlias; public string UserName;
|
|
19
|
+
}'
|
|
20
|
+
}
|
|
21
|
+
`,Ae="COINBASE_KEYRING_SECRET";Gt=class{get(e,r){try{let n=Nt(Kt(e,r)),i=Yt(`
|
|
22
|
+
$ptr = [IntPtr]::Zero
|
|
23
|
+
if ([Win32.Cred]::CredRead('${n}', 1, 0, [ref]$ptr)) {
|
|
24
|
+
$cred = [Runtime.InteropServices.Marshal]::PtrToStructure($ptr, [Type][Win32.Cred+CREDENTIAL])
|
|
25
|
+
$bytes = [byte[]]::new($cred.CredentialBlobSize)
|
|
26
|
+
[Runtime.InteropServices.Marshal]::Copy($cred.CredentialBlob, $bytes, 0, $cred.CredentialBlobSize)
|
|
27
|
+
[Win32.Cred]::CredFree($ptr)
|
|
28
|
+
[Text.Encoding]::UTF8.GetString($bytes)
|
|
29
|
+
}`);return i?rt(i):null}catch{return null}}set(e,r,n){let i=Nt(Kt(e,r)),s=Nt(r);Yt(`
|
|
30
|
+
$sec = $env:${Ae}
|
|
31
|
+
$bytes = [Text.Encoding]::UTF8.GetBytes($sec)
|
|
32
|
+
$blob = [Runtime.InteropServices.Marshal]::AllocHGlobal($bytes.Length)
|
|
33
|
+
[Runtime.InteropServices.Marshal]::Copy($bytes, 0, $blob, $bytes.Length)
|
|
34
|
+
$cred = New-Object Win32.Cred+CREDENTIAL
|
|
35
|
+
$cred.Type = 1
|
|
36
|
+
$cred.TargetName = '${i}'
|
|
37
|
+
$cred.UserName = '${s}'
|
|
38
|
+
$cred.CredentialBlobSize = $bytes.Length
|
|
39
|
+
$cred.CredentialBlob = $blob
|
|
40
|
+
$cred.Persist = 2
|
|
41
|
+
if (-not [Win32.Cred]::CredWrite([ref]$cred, 0)) {
|
|
42
|
+
throw "CredWrite failed: $([Runtime.InteropServices.Marshal]::GetLastWin32Error())"
|
|
43
|
+
}
|
|
44
|
+
[Runtime.InteropServices.Marshal]::FreeHGlobal($blob)`,et(n))}delete(e,r){try{Yt(`[void][Win32.Cred]::CredDelete('${Nt(Kt(e,r))}', 1, 0)`)}catch{}}}});function dt(){if($!==void 0)return $;try{process.platform==="darwin"?$=new St:process.platform==="linux"?$=new wt:process.platform==="win32"?$=new Gt:$=null}catch{Se||(process.stderr.write(`warning: OS keychain not accessible \u2014 secrets stored in keychain will not be available
|
|
45
|
+
`),Se=!0),$=null}return $}function Ce(t){let e=dt();if(!e)return null;try{return e.get(Q,t)}catch{return null}}function we(t,e){let r=dt();if(!r)return!1;try{return r.set(Q,t,e),!0}catch{return!1}}function Ne(t){let e=dt();if(e)try{e.delete(Q,t)}catch{}}function Ge(){return dt()!==null}function De(){let t=dt();if(!t)return!1;let e="__coinbase_cli_preflight__";try{t.set(Q,e,"ok");let r=t.get(Q,e);return t.delete(Q,e),r==="ok"}catch{try{t.delete(Q,e)}catch{}return!1}}var Q,$,Se,Qt=C(()=>{"use strict";u();g();ve();Te();xe();Q="coinbase-cli",Se=!1});var Ve={};Ht(Ve,{activeEnvName:()=>at,activeEnvironment:()=>mt,authIsViable:()=>Jt,builtinEnvironments:()=>D,clearEnvironmentSecretsFromKeychain:()=>Zt,clearHistory:()=>yt,configDir:()=>H,configPath:()=>L,credentialsFor:()=>lt,envCredential:()=>pt,getHistory:()=>ne,historyEnabled:()=>Lt,hydrateEnvironmentSecrets:()=>Fe,isBuiltinURL:()=>st,loadConfig:()=>w,resolveURL:()=>U,saveConfig:()=>q,scrubbedEnv:()=>ee,setHistory:()=>re,setLockOptions:()=>Gn,withConfigLock:()=>te,writeEnvironmentSecretsToKeychain:()=>ze});import{readFileSync as Ue,writeFileSync as qe,mkdirSync as Be,renameSync as An,unlinkSync as Xt,openSync as xn,closeSync as Sn,statSync as Cn}from"node:fs";import{homedir as Le}from"node:os";import{join as Dt}from"node:path";function H(){if(process.env.COINBASE_CONFIG_DIR)return process.env.COINBASE_CONFIG_DIR;if(process.platform==="win32"){let t=process.env.APPDATA??Dt(Le(),"AppData","Roaming");return Dt(t,"coinbase")}return Dt(Le(),".config","coinbase")}function L(){return Dt(H(),"config.json")}function U(t,e){if(t.url)return t.url;if(D[e])return D[e];let r=Me[e];if(r&&D[r])return D[r];let n={...D};for(let[s,o]of Object.entries(Me))D[o]&&(n[s]=D[o]);let i=Object.keys(n).find(s=>e.startsWith(s+"-"));return i?n[i]:""}function st(t){return Object.values(D).some(e=>t.startsWith(e))}function wn(t,e){return Object.values(ot[e].secrets).map(r=>r(t))}function Jt(t){if(!t)return!1;let e=ot[t.type];return e?Object.keys(e.secrets).every(r=>!!t[r]):!1}function Fe(t,e){if(!e.auth)return e;let r=ot[e.auth.type];if(!r)return e;let n=e.auth,i={...n},s=!1,o=!1;for(let[a,d]of Object.entries(r.secrets)){if(n[a]){o=!0;continue}let c=Ce(d(t));c&&(i[a]=c,s=!0)}return Jt(i)?{...e,auth:i,...o&&!s?{_plaintextBacked:!0}:{}}:{...e,auth:void 0}}function ze(t,e){if(!e.auth)return!0;if(!Ge())return!1;let r=e.auth.type,n=ot[r];if(!n)return!0;if(!Jt(e.auth)){let s=Object.keys(n.secrets).filter(o=>!e.auth[o]);throw new Error(`writeEnvironmentSecretsToKeychain: ${r} for "${t}" missing ${s.join(", ")}`)}let i=!0;for(let[s,o]of Object.entries(n.secrets))we(o(t),e.auth[s])||(i=!1);return i}function Zt(t){for(let e of Object.keys(ot))for(let r of wn(t,e))Ne(r)}function pt(t){return t.auth}function lt(t,e){if(e.auth?.type==="cdp_key")return{keyId:e.auth.key_id,keySecret:e.auth.key_secret};throw new Error(`environment "${t}" has no credentials. Run: coinbase env ${t} --key-file path/to/key.json`)}function w(){let t={env:"",environments:{},history:{}};try{let e=Ue(L(),"utf8"),r=JSON.parse(e);t.env=r.env??"";let n=r.environments??{};for(let[i,s]of Object.entries(n))t.environments[i]=Fe(i,s);return t.history=r.history??{},r.settings&&(t.settings=r.settings),t}catch(e){if(e.code!=="ENOENT")throw e instanceof SyntaxError?new Error(`invalid config JSON: ${e.message}`):new Error(`reading config: ${e.message}`)}return t}function q(t,e={}){let r=H();Be(r,{recursive:!0,mode:448});let n={},i=[];for(let[d,c]of Object.entries(t.environments)){let _={};if(c.url!==void 0&&(_.url=c.url),!c.auth){n[d]=_;continue}let l=ze(d,c),I=e.allowPlaintext===!0||c._plaintextBacked===!0;if(!l){if(!I){i.push(d);continue}_.auth=c.auth,n[d]=_;continue}let O=ot[c.auth.type];O&&(_.auth=O.metadataOnly(c.auth)),n[d]=_}if(i.length)throw new Error(`OS keychain unavailable; refusing to persist secrets for environment(s): ${i.join(", ")}.
|
|
46
|
+
Re-run the env-setup command with --allow-plaintext-secrets to write secrets to ${L()} (file is mode 0600), or install/unlock your OS keychain (macOS Keychain, libsecret on Linux, Windows Credential Manager).`);let s={env:t.env,environments:n};Object.keys(t.history).length>0&&(s.history=t.history),t.settings&&(s.settings=t.settings);let o=L(),a=`${o}.${process.pid}.${Date.now()}.tmp`;try{qe(a,JSON.stringify(s,null,2)+`
|
|
47
|
+
`,{mode:384}),An(a,o)}catch(d){try{Xt(a)}catch{}throw d}}function Gn(t){nt={...nt,...t}}function it(){return L()+Nn}function Dn(t){let e=Date.now()+t;for(;Date.now()<e;);}function Ln(){try{let t=xn(it(),"wx",384);return qe(t,String(process.pid)),t}catch(t){if(t.code!=="EEXIST")throw t;return null}}function Mn(){try{let t=Cn(it());if(Date.now()-t.mtimeMs<nt.staleAfterMs)return!1;let e=Ue(it(),"utf8").trim(),r=Number(e);if(!Number.isInteger(r)||r<=0)return!0;try{return process.kill(r,0),!1}catch(n){return n.code==="ESRCH"}}catch{return!1}}function te(t){Be(H(),{recursive:!0,mode:448});let e=null;for(let r=0;r<nt.retries&&(e=Ln(),e===null);r++){if(Mn()){try{Xt(it())}catch{}continue}Dn(nt.baseDelayMs*(1+Math.random()))}if(e===null)throw new Error(`could not acquire config lock after ${nt.retries} attempts: ${it()}`);try{return t()}finally{try{Sn(e)}catch{}try{Xt(it())}catch{}}}function at(t,e){return e||process.env.COINBASE_ENV||t.env}function mt(t,e){let r=at(t,e);if(!r)throw new Error("no environment configured. Run: coinbase env <name>");let n={...t.environments[r]},i=process.env.COINBASE_KEY_ID,s=process.env.COINBASE_KEY_SECRET;if(s&&delete process.env.COINBASE_KEY_SECRET,i&&delete process.env.COINBASE_KEY_ID,i||s){let a=n.auth?.type==="cdp_key"?n.auth:void 0,d=i??a?.key_id??"",c=s??a?.key_secret??"";d&&c&&(n.auth={type:"cdp_key",key_id:d,key_secret:c})}if(process.env.COINBASE_URL&&(n.url=process.env.COINBASE_URL,st(n.url)||process.stderr.write(`warning: COINBASE_URL overrides to non-standard endpoint: ${n.url}
|
|
48
|
+
`)),!U(n,r))throw new Error(`no URL for environment "${r}". Set url in config or use a built-in env name`);if(!n.auth)throw new Error(`no credentials for "${r}". Run: coinbase env ${r} --key-file path/to/key.json`);return{name:r,env:n}}function ee(){let t={...process.env};for(let e of Un)delete t[e];return t}function Lt(t){return process.env.COINBASE_NO_HISTORY?!1:t.settings?.history?.enabled!==!1}function re(t,e,r,n){t.history[e]||(t.history[e]={}),t.history[e][r]={last_used:new Date().toISOString(),values:n}}function ne(t,e,r){return t.history[e]?.[r]}function yt(t,e,r){if(e&&r)return t.history[e]?.[r]?(delete t.history[e][r],Object.keys(t.history[e]).length===0&&delete t.history[e],1):0;if(e){let i=Object.keys(t.history[e]??{}).length;return delete t.history[e],i}let n=Object.values(t.history).reduce((i,s)=>i+Object.keys(s).length,0);return t.history={},n}var D,Me,ot,Nn,nt,Un,M=C(()=>{"use strict";u();g();vt();Qt();D=ke,Me=Re;ot={cdp_key:{secrets:{key_secret:t=>`${t}:cdp:key_secret`},metadataOnly:t=>({type:"cdp_key",key_id:t.key_id})}};Nn=".lock",nt={retries:50,baseDelayMs:20,staleAfterMs:5e3};Un=["COINBASE_KEY_SECRET","COINBASE_KEY_ID"]});var Wt={};Ht(Wt,{bodyBytes:()=>gs,hasBody:()=>us,isFieldArg:()=>ps,parseFields:()=>_s,setNested:()=>Ie});import{readFileSync as ns,openSync as is,readSync as ss,closeSync as os}from"node:fs";function _s(t,e=as){let r={body:{},query:new URLSearchParams,headers:{},rawBody:null};for(let n of t)if(n==="-")r.rawBody=e.readStdin();else if(n.startsWith("@"))r.rawBody=e.readFile(n.slice(1));else if(cs(n)){let i=n.indexOf(":");r.headers[n.slice(0,i)]=n.slice(i+1).trim()}else{let i=n.indexOf("=");if(i<=0)throw new Error(`unrecognized argument: ${n} (use key=value, key:=json, key==query, or Header:value)`);if(n[i-1]===":"){let s=n.slice(0,i-1);try{Ie(r.body,s,JSON.parse(n.slice(i+1)))}catch(o){throw new Error(`invalid JSON in ${s}: ${o.message}`)}}else n[i+1]==="="?r.query.append(n.slice(0,i),n.slice(i+2)):Ie(r.body,n.slice(0,i),n.slice(i+1))}return r}function cs(t){let e=t.indexOf(":");return e<=0||t.slice(0,e).includes("=")?!1:t[0]>="A"&&t[0]<="Z"}function us(t){return Object.keys(t.body).length>0||t.rawBody!==null}function gs(t){return t.rawBody?t.rawBody:Object.keys(t.body).length===0?null:new TextEncoder().encode(JSON.stringify(t.body))}function ps(t){return!!(t.includes("=")||t.startsWith("@")||t.startsWith("-")||t.indexOf(":")>0&&t[0]>="A"&&t[0]<="Z")}function Ie(t,e,r){let n=e.split("."),i=t;for(let s=0;s<n.length;s++){let o=n[s];if(ds.has(o))throw new Error(`invalid field name: ${o}`);if(s===n.length-1){Object.defineProperty(i,o,{value:r,writable:!0,enumerable:!0,configurable:!0});return}(!Object.hasOwn(i,o)||typeof i[o]!="object"||i[o]===null)&&Object.defineProperty(i,o,{value:{},writable:!0,enumerable:!0,configurable:!0}),i=i[o]}}var as,ds,$t=C(()=>{"use strict";u();g();as={readFile(t){try{return ns(t)}catch(e){throw new Error(`reading file ${t}: ${e.message}`)}},readStdin(){let t=[],e=Buffer.alloc(65536),r;try{r=is("/dev/stdin","r")}catch{throw new Error("reading stdin: cannot open")}try{let n;for(;(n=ss(r,e))>0;)t.push(Buffer.from(e.subarray(0,n)))}catch{}return os(r),Buffer.concat(t)}};ds=new Set(["__proto__","constructor","prototype"])});import{execFileSync as ls}from"node:child_process";function mn(t,e){try{return ls("jq",["-r",e],{input:t,encoding:"utf8",maxBuffer:10*1024*1024,env:ee()}).trimEnd()}catch(r){if(r.code==="ENOENT")throw new Error("jq is not installed. Install it: https://jqlang.github.io/jq/download/");let n=(r.stderr??r.message??"").toString().trim();throw new Error(`jq: ${n}`)}}var yn=C(()=>{"use strict";u();g();M()});var bn={};Ht(bn,{outputResult:()=>ms});function ms(t,e){let r=JSON.stringify(t.data),n=t.status>=400;if(e)try{process.stdout.write(mn(new TextEncoder().encode(r),e)+`
|
|
49
|
+
`)}catch(i){process.stdout.write(r+`
|
|
50
|
+
`),process.stderr.write(`jq error: ${i.message}
|
|
51
|
+
`)}else n&&process.stdout.isTTY||(process.stdout.isTTY?process.stdout.write(JSON.stringify(t.data,null,2)+`
|
|
52
|
+
`):process.stdout.write(r+`
|
|
53
|
+
`));if(n){let i=t.data?.message||t.data?.errorMessage||r.slice(0,200),s=t.data?.correlationId,o=t.headers?.get("trace-id"),a=[o?`trace ${o}`:"",s?`correlation ${s}`:""].filter(Boolean).join(", ");throw new Error(`${i} (HTTP ${t.status})${a?` [${a}]`:""}`)}}var fn=C(()=>{"use strict";u();g();yn()});u();g();u();g();M();Qt();import{createInterface as se}from"node:readline/promises";import{readFileSync as qn}from"node:fs";function _t(t,e={}){te(()=>{let r=w();t(r),q(r,{allowPlaintext:e.allowPlaintext})})}function Bn(t){let e=[];if(t.keyFile&&e.push("key-file"),(t.keyId||t.keySecret)&&e.push("key-id/--key-secret"),t.status&&e.push("status"),t.remove&&e.push("remove"),e.length>1)throw new Error(`--${e.join(", --")} are mutually exclusive on \`coinbase env\`. Pick one.`)}function ie(t,e){if(!e&&!De())throw new Error(`OS keychain unavailable; refusing to persist secrets for "${t}".
|
|
54
|
+
Re-run with --allow-plaintext-secrets to write secrets to ${L()} (file is mode 0600), or install/unlock your OS keychain (macOS Keychain, libsecret on Linux, Windows Credential Manager).`)}async function We(t,e){Bn(e);let r=w();if(t.length===0){let _=[...new Set([...Object.keys(r.environments),...Object.keys(D)])].sort();if(_.length===0){process.stderr.write(`No environments configured. Run: coinbase env <name>
|
|
55
|
+
`);return}let l=Math.max(14,..._.map(I=>I.length));for(let I of _){let O=r.environments[I]??{},T=U(O,I),h=pt(O),N=!!r.environments[I],v=h?.type==="cdp_key"?h.key_id:"(no key)",G=v.length>60?v.slice(0,57)+"...":v;process.stdout.write(`${I===r.env?"* ":" "}${I.padEnd(l)} ${(T.length>45?T.slice(0,42)+"...":T).padEnd(45)} ${G}
|
|
56
|
+
`)}return}let n=t[0];if(e.remove){_t(_=>{delete _.environments[n],_.env===n&&(_.env="")}),Zt(n),process.stderr.write(`Removed ${n}.
|
|
57
|
+
`);return}let i=r.environments[n]??{};if(e.status){Vn(n,i);return}let s=(_,l)=>{i.auth={type:"cdp_key",key_id:_,key_secret:l}};if(e.keyFile){ie(n,!!e.allowPlaintextSecrets);let _=Wn(e.keyFile);s(_.keyId,_.keySecret),e.url&&(i.url=e.url,st(e.url)||process.stderr.write(`warning: custom URL does not match a known endpoint
|
|
58
|
+
`)),_t(l=>{l.environments[n]=i,l.env=n},{allowPlaintext:e.allowPlaintextSecrets}),process.stderr.write(`Loaded key from ${e.keyFile}. Switched to ${n}.
|
|
59
|
+
`);return}if(e.keyId||e.keySecret||e.url){let _=pt(i),l=_?.type==="cdp_key"?_.key_id:"",I=_?.type==="cdp_key"?_.key_secret:"";(e.keyId||e.keySecret)&&(ie(n,!!e.allowPlaintextSecrets),s(e.keyId??l,e.keySecret??I)),e.url&&(i.url=e.url,st(e.url)||process.stderr.write(`warning: custom URL does not match a known endpoint
|
|
60
|
+
`)),_t(O=>{O.environments[n]=i,O.env=n},{allowPlaintext:e.allowPlaintextSecrets}),process.stderr.write(`Saved and switched to ${n}.
|
|
61
|
+
`);return}if(pt(i)){_t(_=>{_.env=n}),process.stderr.write(`Switched to ${n}.
|
|
62
|
+
`);return}if(r.environments[n]){_t(_=>{_.env=n}),process.stderr.write(`Switched to ${n} (no credentials yet).
|
|
63
|
+
`),process.stderr.write(` CDP key: coinbase env ${n} --key-file path/to/key.json
|
|
64
|
+
`);return}if(!process.stdout.isTTY)throw new Error(`environment "${n}" needs API keys. Run: coinbase env ${n} --key-id=... --key-secret=...`);ie(n,!!e.allowPlaintextSecrets);let a="",d="",c=se({input:process.stdin,output:process.stderr});try{if(!a){let _=await c.question("API Key ID: ");if(!_.trim())throw new Error("API Key ID is required");a=_.trim()}if(!d){c.close();let _=await Fn("API Key Secret: ");if(!_.trim())throw new Error("API Key Secret is required");d=_.trim(),c=se({input:process.stdin,output:process.stderr})}if(s(a,d),!i.url&&!U(i,n)){let _=await c.question("URL (optional): ");_.trim()&&(i.url=_.trim(),st(i.url)||process.stderr.write(`warning: custom URL does not match a known endpoint
|
|
65
|
+
`))}}finally{c.close()}_t(_=>{_.environments[n]=i,_.env=n},{allowPlaintext:e.allowPlaintextSecrets}),process.stderr.write(`Saved. Switched to ${n}.
|
|
66
|
+
`)}function Fn(t){return process.stderr.write(t),process.stdin.isTTY?new Promise(e=>{let r=process.stdin;r.setRawMode(!0),r.resume(),r.setEncoding("utf8");let n="",i=s=>{s===`
|
|
67
|
+
`||s==="\r"||s===""?(r.setRawMode(!1),r.pause(),r.removeListener("data",i),process.stderr.write(`
|
|
68
|
+
`),e(n)):s==="\x7F"||s==="\b"?n=n.slice(0,-1):s===""?process.exit(1):n+=s};r.on("data",i)}):new Promise(e=>{let r=se({input:process.stdin});r.once("line",n=>{r.close(),e(n)})})}function zn(t){return t?t.length<=12?"***":t.slice(0,8)+"\u2026"+t.slice(-4):"(none)"}function Vn(t,e){(!e.auth||e.auth.type!=="cdp_key")&&(process.stderr.write(`Environment "${t}" has no stored credentials.
|
|
69
|
+
`),process.exit(1)),process.stdout.write(`env: ${t}
|
|
70
|
+
`),process.stdout.write(`auth: cdp_key
|
|
71
|
+
`),process.stdout.write(`key_id: ${zn(e.auth.key_id)}
|
|
72
|
+
`)}function Wn(t){let e;try{e=qn(t,"utf-8")}catch{throw new Error(`cannot read key file: ${t}`)}let r;try{r=JSON.parse(e)}catch{throw new Error(`key file is not valid JSON: ${t}`)}let n=r.name??r.id,i=r.privateKey??r.secret;if(!n||!i)throw new Error(`key file must contain name/privateKey (or id/secret): ${t}`);return{keyId:n,keySecret:i}}u();g();import{createInterface as Mi}from"node:readline";u();g();var j=[{name:"convert",actions:[{id:"convert_quote",name:"quote",method:"POST",path:"/convert/quote",summary:"Get Convert Quote",description:"",pathParams:[],params:[{name:"from",in:"body",type:"string",required:!0,description:"Source currency",example:"USDC"},{name:"to",in:"body",type:"string",required:!0,description:"Target currency",example:"USD"},{name:"amount",in:"body",type:"string",required:!0,description:"Amount to convert",example:"100.00"}],streamable:!1,template:{from:"USDC",to:"USD",amount:"100.00"},responseFields:{fields:["trade"],nested:{trade:{fields:["id","status","amount","subtotal","total","total_fee","exchange_rate","source_currency","target_currency"]}}}},{id:"convert_execute",name:"execute",method:"POST",path:"/convert/execute/{quote_id}",summary:"Execute Convert Trade",description:"",pathParams:["quote_id"],params:[{name:"quote_id",in:"path",type:"string",required:!0,description:"Quote ID from convert quote response"},{name:"from",in:"body",type:"string",required:!0,description:"Source currency",example:"USDC"},{name:"to",in:"body",type:"string",required:!0,description:"Target currency",example:"USD"}],streamable:!1,template:{from:"USDC",to:"USD"},responseFields:{fields:["trade"],nested:{trade:{fields:["id","status"]}}}},{id:"convert_get",name:"get",method:"GET",path:"/convert/get/{quote_id}",summary:"Get Convert Status",description:"Check status of a conversion by quote ID.",pathParams:["quote_id"],params:[{name:"quote_id",in:"path",type:"string",required:!0,description:"Quote ID"},{name:"from",in:"query",type:"string",required:!0,description:"Source currency"},{name:"to",in:"query",type:"string",required:!0,description:"Target currency"}],streamable:!1,template:{},responseFields:{fields:["id","status","source_currency","target_currency","exchange_rate"]}}]},{name:"orders",actions:[{id:"orders_list",name:"list",method:"GET",path:"/orders",summary:"List Orders",description:"",pathParams:[],params:[{name:"status",in:"query",type:"string",required:!1,description:"Filter by status"},{name:"product_ids",in:"query",type:"string",required:!1,description:"Filter by product (e.g. BTC-USD). Comma-separated for multiple."},{name:"side",in:"query",type:"string",required:!1,description:"Filter by side",enum:["BUY","SELL"]},{name:"product_type",in:"query",type:"string",required:!1,description:"Filter by product type",enum:["SPOT","FUTURE"]},{name:"start",in:"query",type:"string",required:!1,description:"Start date (RFC 3339 with timezone, e.g. 2024-01-01T00:00:00Z)",format:"date-time"},{name:"end",in:"query",type:"string",required:!1,description:"End date (RFC 3339 with timezone, e.g. 2024-12-31T23:59:59Z)",format:"date-time"},{name:"limit",in:"query",type:"integer",required:!1,description:"Results per page"},{name:"cursor",in:"query",type:"string",required:!1,description:"Pagination cursor"}],streamable:!1,template:{},responseFields:{fields:["orders","has_next","cursor"],nested:{orders:{fields:["order_id","product_id","side","status","order_configuration","created_time","completion_percentage","filled_size","average_filled_price","total_fees","total_value_after_fees","number_of_fills"]}}}},{id:"orders_create",name:"create",method:"POST",path:"/orders",summary:"Create Order",description:"",pathParams:[],params:[{name:"product_id",in:"body",type:"string",required:!0,description:"Trading pair",example:"BTC-USD"},{name:"side",in:"body",type:"string",required:!0,description:"BUY or SELL",enum:["BUY","SELL"],example:"BUY"},{name:"type",in:"body",type:"string",required:!0,description:"market (fill at current price), limit (fill at specified price or better), stop_limit (trigger limit order when price crosses stop_price)",enum:["market","limit","stop_limit"],example:"market"},{name:"quote_size",in:"body",type:"string",required:!1,description:"Quote currency amount (e.g. spend $100 USD). Market buy only. Mutually exclusive with base_size.",oneOf:"base_size|quote_size",example:"100.00"},{name:"base_size",in:"body",type:"string",required:!1,description:"Base currency amount (e.g. buy 0.001 BTC). Any side/type.",oneOf:"base_size|quote_size",example:"0.001"},{name:"limit_price",in:"body",type:"string",required:!1,description:"Limit price (limit/stop_limit orders)"},{name:"stop_price",in:"body",type:"string",required:!1,description:"Stop price (stop_limit orders)"},{name:"stop_direction",in:"body",type:"string",required:!1,description:"up = trigger when price rises above stop_price. down = trigger when falls below.",enum:["up","down"]},{name:"post_only",in:"body",type:"boolean",required:!1,description:"Post-only mode (limit orders). Rejects if would take liquidity."},{name:"time_in_force",in:"body",type:"string",required:!1,description:"GTC=good til cancelled. IOC=immediate or cancel. FOK=fill or kill.",enum:["GTC","IOC","FOK","GTD"]},{name:"end_time",in:"body",type:"string",required:!1,description:"Expiry time for GTD orders (RFC 3339). Required when time_in_force=GTD.",format:"date-time"},{name:"reduce_only",in:"body",type:"boolean",required:!1,description:"Only reduce existing position. Rejects if would increase exposure."},{name:"portfolio_id",in:"body",type:"string",required:!1,description:"Scopes the order to a specific portfolio (defaults to auth-scoped portfolio)"},{name:"client_order_id",in:"body",type:"string",required:!1,description:"Idempotency key (auto-generated if omitted)"}],streamable:!1,template:{product_id:"BTC-USD",side:"BUY",type:"market",quote_size:"100.00",base_size:"0.001",limit_price:"<string>",stop_price:"<string>",stop_direction:"up",post_only:!1,time_in_force:"GTC",end_time:"<string>",reduce_only:!1,portfolio_id:"<string>",client_order_id:"<string>"},responseFields:{fields:["order_id","product_id","side","client_order_id"]}},{id:"orders_preview",name:"preview",method:"POST",path:"/orders/preview",summary:"Preview Order",description:"Estimate fees, fill price, and slippage without executing.",pathParams:[],params:[{name:"product_id",in:"body",type:"string",required:!0,description:"Trading pair (e.g. BTC-USD)",example:"BTC-USD"},{name:"side",in:"body",type:"string",required:!0,description:"BUY or SELL",enum:["BUY","SELL"],example:"BUY"},{name:"type",in:"body",type:"string",required:!0,description:"market (fill at current price), limit (fill at specified price or better), stop_limit (trigger limit order when price crosses stop_price)",enum:["market","limit","stop_limit"],example:"market"},{name:"quote_size",in:"body",type:"string",required:!1,description:"Quote currency amount (e.g. spend $100 USD). Mutually exclusive with base_size.",oneOf:"base_size|quote_size",example:"100.00"},{name:"base_size",in:"body",type:"string",required:!1,description:"Base currency amount (e.g. 0.001 BTC). Mutually exclusive with quote_size.",oneOf:"base_size|quote_size"},{name:"limit_price",in:"body",type:"string",required:!1,description:"Limit price (for limit/stop_limit previews)"},{name:"stop_price",in:"body",type:"string",required:!1,description:"Stop price (for stop_limit previews)"},{name:"stop_direction",in:"body",type:"string",required:!1,description:"up = trigger when price rises above stop_price. down = trigger when falls below.",enum:["up","down"]}],streamable:!1,template:{product_id:"BTC-USD",side:"BUY",type:"market",quote_size:"100.00",base_size:"<string>",limit_price:"<string>",stop_price:"<string>",stop_direction:"up"},responseFields:{fields:["order_total","commission_total","slippage","est_average_filled_price","base_size","quote_size","best_bid","best_ask"]}},{id:"orders_edit",name:"edit",method:"POST",path:"/orders/edit",summary:"Edit Order",description:"Atomically update an open order's price or size without cancelling. Avoids the cancel+replace exposure window.",pathParams:[],params:[{name:"order_id",in:"body",type:"string",required:!0,description:"Order to edit"},{name:"base_size",in:"body",type:"string",required:!1,description:"New base size"},{name:"limit_price",in:"body",type:"string",required:!1,description:"New limit price"}],streamable:!1,template:{order_id:"<string>",base_size:"<string>",limit_price:"<string>"},responseFields:{fields:["order_id","status"]}},{id:"orders_get",name:"get",method:"GET",path:"/orders/{order_id}",summary:"Get Order",description:"",pathParams:["order_id"],params:[{name:"order_id",in:"path",type:"string",required:!0,description:"Order ID"}],streamable:!1,template:{},responseFields:{fields:["order_id","product_id","side","status","order_configuration","created_time","completion_percentage","filled_size","average_filled_price","total_fees","total_value_after_fees","number_of_fills"]}},{id:"orders_cancel",name:"cancel",method:"POST",path:"/orders/cancel",summary:"Cancel Orders",description:"",pathParams:[],params:[{name:"order_ids",in:"body",type:"array",required:!0,description:"Order IDs to cancel"}],streamable:!1,template:{order_ids:[]},responseFields:{fields:["results"],nested:{results:{fields:["success","order_id"]}}}},{id:"orders_close_position",name:"close-position",method:"POST",path:"/orders/close-position",summary:"Close Position",description:"",pathParams:[],params:[{name:"client_order_id",in:"body",type:"string",required:!0,description:"Idempotency key"},{name:"product_id",in:"body",type:"string",required:!0,description:"Product to close"},{name:"size",in:"body",type:"string",required:!1,description:"Partial close size (omit for full close)"}],streamable:!1,template:{client_order_id:"<string>",product_id:"<string>",size:"<string>"},responseFields:null},{id:"orders_fills",name:"fills",method:"GET",path:"/orders/fills",summary:"List Fills",description:"",pathParams:[],params:[{name:"product_ids",in:"query",type:"string",required:!1,description:"Filter by product (e.g. BTC-USD)"},{name:"order_ids",in:"query",type:"string",required:!1,description:"Filter by order ID"},{name:"start",in:"query",type:"string",required:!1,description:"Start time (RFC 3339 with timezone, e.g. 2024-01-01T00:00:00Z)",format:"date-time"},{name:"end",in:"query",type:"string",required:!1,description:"End time (RFC 3339 with timezone, e.g. 2024-12-31T23:59:59Z)",format:"date-time"},{name:"side",in:"query",type:"string",required:!1,description:"Filter by side",enum:["BUY","SELL"]},{name:"limit",in:"query",type:"integer",required:!1,description:"Results per page"},{name:"cursor",in:"query",type:"string",required:!1,description:"Pagination cursor"}],streamable:!1,template:{},responseFields:{fields:["fills","cursor"],nested:{fills:{fields:["trade_id","order_id","trade_time","price","size","commission","product_id","side","liquidity_indicator"]}}}}]},{name:"portfolios",actions:[{id:"portfolios_list",name:"list",method:"GET",path:"/portfolios",summary:"List Portfolios",description:"",pathParams:[],params:[{name:"portfolio_type",in:"query",type:"string",required:!1,description:"Filter by type (DEFAULT, CONSUMER, INTX)"}],streamable:!1,template:{},responseFields:{fields:["portfolios"],nested:{portfolios:{fields:["uuid","name","type","deleted"]}}}},{id:"portfolios_create",name:"create",method:"POST",path:"/portfolios",summary:"Create Portfolio",description:"",pathParams:[],params:[{name:"name",in:"body",type:"string",required:!0,description:"Portfolio name",example:"agent-1"}],streamable:!1,template:{name:"agent-1"},responseFields:{fields:["uuid","name","type","deleted"]}},{id:"portfolios_get",name:"get",method:"GET",path:"/portfolios/{portfolio_id}",summary:"Get Portfolio Breakdown",description:"Returns portfolio metadata, balances, and positions.",pathParams:["portfolio_id"],params:[{name:"portfolio_id",in:"path",type:"string",required:!0,description:"Portfolio UUID"},{name:"currency",in:"query",type:"string",required:!1,description:"Currency for balance denomination"}],streamable:!1,template:{},responseFields:{fields:["portfolio","portfolio_balances","spot_positions"],nested:{portfolio:{fields:["uuid","name","type","deleted"]},portfolio_balances:{fields:["total_balance","total_crypto_balance","total_cash_equivalent_balance"],nested:{total_balance:{fields:["value","currency"]},total_crypto_balance:{fields:["value","currency"]},total_cash_equivalent_balance:{fields:["value","currency"]}}},spot_positions:{fields:["asset","account_uuid","total_balance_fiat","total_balance_crypto","available_to_trade_fiat","allocation","is_cash"]}}}},{id:"portfolios_edit",name:"edit",method:"PUT",path:"/portfolios/{portfolio_id}",summary:"Edit Portfolio",description:"",pathParams:["portfolio_id"],params:[{name:"portfolio_id",in:"path",type:"string",required:!0,description:"Portfolio UUID"},{name:"name",in:"body",type:"string",required:!1,description:"New portfolio name"}],streamable:!1,template:{name:"<string>"},responseFields:{fields:["uuid","name","type","deleted"]}},{id:"portfolios_delete",name:"delete",method:"DELETE",path:"/portfolios/{portfolio_id}",summary:"Delete Portfolio",description:"",pathParams:["portfolio_id"],params:[{name:"portfolio_id",in:"path",type:"string",required:!0,description:"Portfolio UUID"}],streamable:!1,template:{},responseFields:null}]},{name:"products",actions:[{id:"products_list",name:"list",method:"GET",path:"/products",summary:"List Products",description:"",pathParams:[],params:[{name:"symbol",in:"query",type:"string",required:!1,description:"Filter products by symbol, matches base or quote side (e.g. USD, BTC). For a specific product use `get` (e.g. BTC-USD). Pagination ignored when set.",pattern:"^[A-Za-z0-9]+$"},{name:"product_type",in:"query",type:"string",required:!1,description:"Product type filter",enum:["SPOT","FUTURE"]},{name:"product_ids",in:"query",type:"string",required:!1,description:"Comma-separated product IDs"},{name:"limit",in:"query",type:"integer",required:!1,description:"Results per page"},{name:"cursor",in:"query",type:"string",required:!1,description:"Pagination cursor"}],streamable:!1,template:{},responseFields:{fields:["products","cursor","has_next","num_products"],nested:{products:{fields:["product_id","price","price_percentage_change_24h","volume_24h","product_type","quote_currency_id","base_currency_id","base_increment","quote_increment","base_min_size","base_max_size","quote_min_size","quote_max_size","status"]}}}},{id:"products_get",name:"get",method:"GET",path:"/products/{product_id}",summary:"Get Product",description:"",pathParams:["product_id"],params:[{name:"product_id",in:"path",type:"string",required:!0,description:"Product ID (e.g. BTC-USD)"}],streamable:!1,template:{},responseFields:{fields:["product_id","price","price_percentage_change_24h","volume_24h","product_type","quote_currency_id","base_currency_id","base_increment","quote_increment","base_min_size","base_max_size","quote_min_size","quote_max_size","status"]}},{id:"products_ticker",name:"ticker",method:"GET",path:"/products/{product_id}/ticker",summary:"Get Ticker",description:"",pathParams:["product_id"],params:[{name:"product_id",in:"path",type:"string",required:!0,description:"Trading pair (e.g. BTC-USD)"},{name:"limit",in:"query",type:"integer",required:!1,description:"Number of trades. Defaults to 100."}],streamable:!0,template:{},responseFields:{fields:["trades","best_bid","best_ask"],nested:{trades:{fields:["trade_id","product_id","price","size","time","side"]}}}},{id:"products_book",name:"book",method:"GET",path:"/products/{product_id}/book",summary:"Get Order Book",description:"",pathParams:["product_id"],params:[{name:"product_id",in:"path",type:"string",required:!0,description:"Product ID"},{name:"limit",in:"query",type:"integer",required:!1,description:"Number of levels"}],streamable:!0,template:{},responseFields:{fields:["pricebook"],nested:{pricebook:{fields:["product_id","bids","asks","time"],nested:{bids:{fields:["price","size"]},asks:{fields:["price","size"]}}}}}},{id:"products_candles",name:"candles",method:"GET",path:"/products/{product_id}/candles",summary:"Get Candles",description:"",pathParams:["product_id"],params:[{name:"product_id",in:"path",type:"string",required:!0,description:"Trading pair (e.g. BTC-USD)"},{name:"start",in:"query",type:"string",required:!1,description:"Start time (RFC 3339, e.g. 2024-01-01T00:00:00Z). Defaults to 60 candles before end.",format:"date-time"},{name:"end",in:"query",type:"string",required:!1,description:"End time (RFC 3339, e.g. 2024-01-01T00:00:00Z). Defaults to now.",format:"date-time"},{name:"granularity",in:"query",type:"string",required:!1,description:"Candle interval: 1m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 1d. Defaults to 1h.",enum:["1m","5m","15m","30m","1h","2h","4h","6h","1d"]},{name:"limit",in:"query",type:"integer",required:!1,description:"Max candles"}],streamable:!0,template:{},responseFields:{fields:["candles"],nested:{candles:{fields:["start","low","high","open","close","volume"]}}}},{id:"products_best_bid_ask",name:"best-bid-ask",method:"GET",path:"/products/best-bid-ask",summary:"Best Bid/Ask for Multiple Products",description:"Returns best bid and ask for one or more products in a single call.",pathParams:[],params:[{name:"product_ids",in:"query",type:"string",required:!1,description:"Comma-separated product IDs (e.g. BTC-USD,ETH-USD)"}],streamable:!1,template:{},responseFields:{fields:["pricebooks"],nested:{pricebooks:{fields:["product_id","bids","asks","time"],nested:{bids:{fields:["price","size"]},asks:{fields:["price","size"]}}}}}}]}],K={balance:{id:"balance",name:"balance",method:"GET",path:"/balance",summary:"View Balances",description:"",pathParams:[],params:[{name:"portfolio_id",in:"query",type:"string",required:!1,description:"Filter by portfolio"},{name:"show_zero",in:"query",type:"boolean",required:!1,description:"Include zero-balance accounts (default false)"},{name:"limit",in:"query",type:"integer",required:!1,description:"Results per page"},{name:"cursor",in:"query",type:"string",required:!1,description:"Pagination cursor"}],streamable:!1,template:{},responseFields:{fields:["accounts","has_next","cursor","size"],nested:{accounts:{fields:["uuid","name","currency","available_balance","hold","type","active","ready"],nested:{available_balance:{fields:["value","currency"]},hold:{fields:["value","currency"]}}}}}},transfer:{id:"transfer",name:"transfer",method:"POST",path:"/transfer",summary:"Move Funds Between Portfolios",description:"",pathParams:[],params:[{name:"amount",in:"body",type:"string",required:!0,description:"Amount to transfer",example:"100.00"},{name:"currency",in:"body",type:"string",required:!0,description:"Currency",example:"USD"},{name:"from",in:"body",type:"string",required:!0,description:"Source portfolio UUID",example:"d17b3486-..."},{name:"to",in:"body",type:"string",required:!0,description:"Target portfolio UUID",example:"a44ef7a8-..."}],streamable:!1,template:{amount:"100.00",currency:"USD",from:"d17b3486-...",to:"a44ef7a8-..."},responseFields:{fields:["source_portfolio_uuid","target_portfolio_uuid"]}},fees:{id:"fees",name:"fees",method:"GET",path:"/fees",summary:"Fee Tier and Volume",description:"Fee tier, maker/taker rates, and 30-day volume.",pathParams:[],params:[{name:"product_type",in:"query",type:"string",required:!1,description:"Filter by product type",enum:["SPOT","FUTURE"]}],streamable:!1,template:{},responseFields:{fields:["total_fees","fee_tier","margin_rate","advanced_trade_only_volume","advanced_trade_only_fees","coinbase_pro_volume","coinbase_pro_fees","total_balance","has_cost_plus_commission"],nested:{fee_tier:{fields:["pricing_tier","taker_fee_rate","maker_fee_rate"]}}}}},$e=new Set(["balance","transfer","fees"]),He={"date-time":/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/};function $n(t,e,r){let n=new Date(Date.UTC(t,e-1,r));return n.getUTCFullYear()===t&&n.getUTCMonth()===e-1&&n.getUTCDate()===r}var je={"date-time":t=>{let e=t.match(/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})/);if(!e)return!1;let[,r,n,i,s,o,a]=e;return $n(+r,+n,+i)&&+s<24&&+o<60&&+a<61}};u();g();u();g();function Ke(t){return typeof t=="object"&&"endpoint"in t}function P(t,e){return{endpoint:t,...e}}u();g();u();g();u();g();var Ye={origin:"RetailBrokerageApi_CancelOrders",method:"POST",path:"/api/v3/brokerage/orders/batch_cancel"},Qe={origin:"RetailBrokerageApi_ClosePosition",method:"POST",path:"/api/v3/brokerage/orders/close_position"},Xe={origin:"RetailBrokerageApi_CommitConvertTrade",method:"POST",path:"/api/v3/brokerage/convert/trade/{trade_id}"};var Je={origin:"RetailBrokerageApi_CreatePortfolio",method:"POST",path:"/api/v3/brokerage/portfolios"},Ze={origin:"RetailBrokerageApi_DeletePortfolio",method:"DELETE",path:"/api/v3/brokerage/portfolios/{portfolio_uuid}"};var tr={origin:"RetailBrokerageApi_EditPortfolio",method:"PUT",path:"/api/v3/brokerage/portfolios/{portfolio_uuid}"};var er={origin:"RetailBrokerageApi_GetBestBidAsk",method:"GET",path:"/api/v3/brokerage/best_bid_ask"};var rr={origin:"RetailBrokerageApi_GetConvertTrade",method:"GET",path:"/api/v3/brokerage/convert/trade/{trade_id}"};var nr={origin:"RetailBrokerageApi_GetFills",method:"GET",path:"/api/v3/brokerage/orders/historical/fills"},ir={origin:"RetailBrokerageApi_GetHistoricalOrder",method:"GET",path:"/api/v3/brokerage/orders/historical/{order_id}"},sr={origin:"RetailBrokerageApi_GetHistoricalOrders",method:"GET",path:"/api/v3/brokerage/orders/historical/batch"};var or={origin:"RetailBrokerageApi_GetPortfolioBreakdown",method:"GET",path:"/api/v3/brokerage/portfolios/{portfolio_uuid}"},ar={origin:"RetailBrokerageApi_GetPortfolios",method:"GET",path:"/api/v3/brokerage/portfolios"},_r={origin:"RetailBrokerageApi_GetProduct",method:"GET",path:"/api/v3/brokerage/products/{product_id}"};var cr={origin:"RetailBrokerageApi_GetPublicProductBook",method:"GET",path:"/api/v3/brokerage/market/product_book"};var ur={origin:"RetailBrokerageApi_GetTransactionSummary",method:"GET",path:"/api/v3/brokerage/transaction_summary"};var gr={portfolios_list:P(ar),portfolios_get:P(or,{requestMap:{portfolio_id:"portfolio_uuid"},responseUnwrap:"breakdown"}),portfolios_create:P(Je,{responseUnwrap:"portfolio"}),portfolios_edit:P(tr,{requestMap:{portfolio_id:"portfolio_uuid"},responseUnwrap:"portfolio"}),portfolios_delete:P(Ze,{requestMap:{portfolio_id:"portfolio_uuid"}})};u();g();import{randomUUID as jn}from"node:crypto";u();g();var R=class t extends Error{status;correlationId;traceId;constructor(e,r,n){super(e),this.status=r,this.correlationId=n?.correlationId,this.traceId=n?.traceId}static stripGrpcPrefix(e){let r=e.match(/^rpc error: code = \w+ desc = (.+)$/s);return r?r[1]:e}static fromResponse(e,r,n){let i=new TextDecoder().decode(e),s,o;try{let d=JSON.parse(i);d.errorType&&d.errorMessage?s=`${d.errorType} \u2014 ${d.errorMessage}`:d.message?s=t.stripGrpcPrefix(d.message):s=i.length>200?i.slice(0,200)+"...":i,o=d.correlationId||d.correlation_id||void 0}catch{s=i.length>200?i.slice(0,200)+"...":i}let a=n?.get("trace-id")??void 0;return new t(s,r,{correlationId:o,traceId:a})}};u();g();function oe(t){if(t?.success===!1){let e=t.error_response,r=t.errors?.[0]?.edit_failure_reason,n=e?.new_order_failure_reason?String(e.new_order_failure_reason).replace(/^NEW_ORDER_FAILURE_REASON_/,"").replace(/_/g," ").toLowerCase():void 0;throw new R(e?.message||e?.error_details||n||r?.toString()||"Operation failed",400)}}u();g();var Mt={MARKET:"market",LIMIT:"limit",STOP_LIMIT:"stop_limit"};var ae={UP:"up",DOWN:"down"},X={GTC:"GTC",IOC:"IOC",FOK:"FOK",GTD:"GTD"};u();g();function Hn(t,e){let r={...e},n=t;for(let[i,s]of Object.entries(r)){let o="{"+i+"}";s!=null&&n.includes(o)&&(n=n.replaceAll(o,encodeURIComponent(String(s))),delete r[i])}return{url:n,remaining:r}}var bt=class extends Error{status;data;constructor(e,r,n){super(e),this.status=r,this.data=n}};async function B(t,e,r,n){let{url:i,remaining:s}=Hn(r,n),o=e==="POST"||e==="PUT"||e==="DELETE"||e==="PATCH",a,d=null;if(o)a=t.baseURL+i,Object.keys(s).length>0&&(d=new TextEncoder().encode(JSON.stringify(s)));else{let I=Object.entries(s).filter(([,O])=>O!=null).map(([O,T])=>encodeURIComponent(O)+"="+encodeURIComponent(String(T))).join("&");a=t.baseURL+i+(I?"?"+I:"")}let c=await t.doRequest({method:e,url:a,body:d}),_=new TextDecoder().decode(c.body),l;try{l=JSON.parse(_)}catch{l=_}if(c.status>=400){let I=l,O=I?.error_response?.message??I?.error_response?.error_details??I?.message??I?.error_details??_.slice(0,200);throw new bt(O,c.status,l)}return{status:c.status,data:l}}function dr(t,e){return B(t,"POST","/api/v3/brokerage/convert/quote",e)}function pr(t,e){return B(t,"POST","/api/v3/brokerage/orders/edit",e)}function lr(t,e){return B(t,"GET","/api/v3/brokerage/accounts",e)}function mr(t,e){return B(t,"GET","/api/v3/brokerage/products/{product_id}/candles",e)}function yr(t,e){return B(t,"GET","/api/v3/brokerage/products/{product_id}/ticker",e)}function _e(t,e){return B(t,"GET","/api/v3/brokerage/products",e)}function br(t,e){return B(t,"POST","/api/v3/brokerage/portfolios/move_funds",e)}function fr(t,e){return B(t,"POST","/api/v3/brokerage/orders",e)}function Er(t,e){return B(t,"POST","/api/v3/brokerage/orders/preview",e)}function Ir(t){let e=t.type;if(e===Mt.MARKET)return{market_market_ioc:{...t.quote_size&&{quote_size:t.quote_size},...t.base_size&&{base_size:t.base_size}}};if(e===Mt.LIMIT){let r=t.time_in_force??X.GTC,n={base_size:t.base_size,limit_price:t.limit_price,...t.post_only&&{post_only:!0}};return r===X.IOC?{sor_limit_ioc:n}:r===X.FOK?{limit_limit_fok:n}:r===X.GTD?{limit_limit_gtd:{...n,...t.end_time&&{end_time:t.end_time}}}:{limit_limit_gtc:n}}else if(e===Mt.STOP_LIMIT){let r=t.stop_direction===ae.UP?"STOP_DIRECTION_STOP_UP":t.stop_direction===ae.DOWN?"STOP_DIRECTION_STOP_DOWN":(()=>{throw new R("stop_direction is required for stop_limit orders (up or down)",400)})(),n=t.time_in_force??X.GTC,i={base_size:t.base_size,limit_price:t.limit_price,stop_price:t.stop_price,stop_direction:r};return n===X.GTD?{stop_limit_stop_limit_gtd:{...i,...t.end_time&&{end_time:t.end_time}}}:{stop_limit_stop_limit_gtc:i}}throw new R(`Unknown order type: ${e}. Use market, limit, or stop_limit.`,400)}function Kn(t){let e={client_order_id:t.client_order_id??jn(),product_id:t.product_id,side:t.side,order_configuration:Ir(t)};return t.portfolio_id&&(e.retail_portfolio_id=t.portfolio_id),t.reduce_only&&(e.reduce_only=!0),e}var Yn=async(t,e)=>{let r=Kn(t),n=await fr(e,r);return oe(n.data),n.data.success_response??n.data},Qn=async(t,e)=>{let r=await Er(e,{product_id:t.product_id,side:t.side,order_configuration:Ir(t)}),n=r.data;if(n.errs?.length>0)throw new R(n.errs[0].replace(/^PREVIEW_/,"").replace(/_/g," ").toLowerCase(),400);return r.data},Xn=async(t,e)=>{if(!t.base_size&&!t.limit_price)throw new R("Provide base_size and/or limit_price to edit",400);let r={order_id:t.order_id,size:t.base_size??"",price:t.limit_price??""},n=await pr(e,r);return oe(n.data),{order_id:t.order_id,status:"EDITED"}},Or={orders_list:P(sr,{requestMap:{status:"order_status",side:"order_side",start:"start_date",end:"end_date"}}),orders_get:P(ir,{responseUnwrap:"order"}),orders_create:Yn,orders_preview:Qn,orders_edit:Xn,orders_cancel:P(Ye),orders_close_position:P(Qe),orders_fills:P(nr,{requestMap:{side:"order_side",start:"start_sequence_timestamp",end:"end_sequence_timestamp"}})};u();g();var Jn={"1m":"ONE_MINUTE","5m":"FIVE_MINUTE","15m":"FIFTEEN_MINUTE","30m":"THIRTY_MINUTE","1h":"ONE_HOUR","2h":"TWO_HOUR","4h":"FOUR_HOUR","6h":"SIX_HOUR","1d":"ONE_DAY"},Zn={"1m":60,"5m":300,"15m":900,"30m":1800,"1h":3600,"2h":7200,"4h":14400,"6h":21600,"1d":86400},ti=60,ei=async(t,e)=>{let{symbol:r,limit:n,cursor:i,...s}=t;if(r){let a=r.toUpperCase(),_=((await _e(e,s)).data.products??[]).filter(I=>I.base_currency_id===a||I.quote_currency_id===a),l=n!==void 0&&n>0?_.slice(0,n):_;return{products:l,num_products:l.length}}return(await _e(e,{...s,limit:n,cursor:i})).data};function hr(t){return String(Math.floor(new Date(t).getTime()/1e3))}var ri=async(t,e)=>{let r=t.granularity??"1h",n=Jn[r],i=Zn[r],s=Math.floor(Date.now()/1e3),o=t.end?hr(t.end):String(s),a=t.start?hr(t.start):String(Number(o)-ti*i);return(await mr(e,{...t,granularity:n,start:a,end:o})).data},ni=async(t,e)=>{let r=t.limit??100;return(await yr(e,{...t,limit:r})).data},Pr={products_list:ei,products_get:P(_r),products_ticker:ni,products_book:P(cr),products_candles:ri,products_best_bid_ask:P(er)};u();g();var ii=async(t,e)=>{let{from:r,to:n,...i}=t,s=await dr(e,{...i,from_account:r,to_account:n});if(!s.data?.trade?.id)throw new R(`Convert quote returned no trade for ${t.from} \u2192 ${t.to}. Verify balance, supported pair, and account eligibility.`,422);return s.data},kr={convert_quote:ii,convert_execute:P(Xe,{requestMap:{quote_id:"trade_id",from:"from_account",to:"to_account"}}),convert_get:P(rr,{requestMap:{quote_id:"trade_id",from:"from_account",to:"to_account"},responseUnwrap:"trade"})};u();g();var si=async(t,e)=>{let r={};t.portfolio_id&&(r.retail_portfolio_id=t.portfolio_id),t.limit&&(r.limit=t.limit),t.cursor&&(r.cursor=t.cursor);let i=(await lr(e,r)).data,s=i.accounts??[],o=t.show_zero?s:s.filter(a=>parseFloat(a.available_balance?.value??"0")!==0||parseFloat(a.hold?.value??"0")!==0);return{accounts:o,has_next:i.has_next,cursor:i.cursor,size:o.length}},Rr={balance:si};u();g();var oi=async(t,e)=>{let r={funds:{value:t.amount,currency:t.currency},source_portfolio_uuid:t.from,target_portfolio_uuid:t.to},i=(await br(e,r)).data;if(!i?.source_portfolio_uuid)throw new R("Transfer failed \u2014 check amount, currency, and portfolio UUIDs",400);return i},vr={transfer:oi};u();g();var Tr={fees:P(ur)};var ai={...gr,...Or,...Pr,...kr,...Rr,...vr,...Tr};function Ut(t){return ai[t]}u();g();function ce(t,e){if(!e)return t;let r={};for(let[n,i]of Object.entries(t)){let s=e[n]??n;r[s]=i}return r}function Ar(t,e){if(!e||typeof t!="object"||t===null||Array.isArray(t))return t;let r={};for(let[n,i]of Object.entries(t)){let s=e[n]??n;r[s]=i}return r}var xr=[...j.flatMap(t=>t.actions),...Object.values(K)],ue={},Sr=new Map;for(let t of xr){ue[t.id]=t.responseFields;for(let e of t.params)e.pattern&&Sr.set(e.pattern,new RegExp(e.pattern))}function _i(t,e){let r={...e},n=t;for(let[i,s]of Object.entries(e)){if(s==null||s==="")continue;let o=`{${i}}`;n.includes(o)&&(n=n.replaceAll(o,encodeURIComponent(String(s))),delete r[i])}return{resolvedPath:n,remainingParams:r}}function ci(t){let e=[];for(let[r,n]of Object.entries(t))if(n!=null)if(Array.isArray(n))for(let i of n)e.push(`${encodeURIComponent(r)}=${encodeURIComponent(String(i))}`);else e.push(`${encodeURIComponent(r)}=${encodeURIComponent(String(n))}`);return e.length>0?"?"+e.join("&"):""}function ui(t,e){let r=e.split("."),n=t;for(let i of r){if(n==null||typeof n!="object")return t;n=n[i]}return n!==void 0?n:t}function gi(t){return t&&typeof t=="object"&&("body"in t||"query"in t||"path"in t||"rawBody"in t)}function ft(t,e){if(t==null||typeof t!="object")return t;if(Array.isArray(t))return t.map(n=>ft(n,e));let r={};for(let n of e.fields){if(!(n in t))continue;let i=e.nested?.[n];i&&Array.isArray(t[n])?r[n]=t[n].map(s=>ft(s,i)):i&&typeof t[n]=="object"&&t[n]!==null?r[n]=ft(t[n],i):r[n]=t[n]}return r}function di(t,e){let r=ue[t];if(!r)return;if(!e)return r;let n=r;for(let i of e.split(".")){let s=n?.nested?.[i];if(!s)return r;n=s}return n}async function qt(t,e,r){let n=gi(e),i=n?e.body??{}:e,s=n?e.query??{}:{},o=n?e.path??{}:{},a=n?e.headers??{}:{},d=n?e.rawBody??null:null,c=Ut(t);if(!c)return{status:404,data:{error:"UNKNOWN_OPERATION",code:404,message:`No route for ${t}`}};let _=xr.find(b=>b.id===t);if(_?.params){if(d)try{let b=JSON.parse(new TextDecoder().decode(d));b&&typeof b=="object"&&!Array.isArray(b)&&Object.assign(i,b)}catch{}{let p=_.params.filter(f=>f.in==="body"&&f.required).filter(f=>i[f.name]===void 0||i[f.name]==="");if(p.length>0)return{status:400,data:{error:"MISSING_FIELDS",code:400,message:`Missing required field(s): ${p.map(E=>E.name).join(", ")}`}}}{let b={...o,...i,...s},p={},f={};for(let E of _.params)if(E.oneOf&&(f[E.oneOf]||(f[E.oneOf]=[]),f[E.oneOf].push(E.name),b[E.name]!==void 0&&b[E.name]!=="")){if(p[E.oneOf])return{status:400,data:{error:"CONFLICTING_FIELDS",code:400,message:`${p[E.oneOf]} and ${E.name} are mutually exclusive. Provide one, not both.`}};p[E.oneOf]=E.name}for(let[E,x]of Object.entries(f))if(!p[E])return{status:400,data:{error:"MISSING_FIELDS",code:400,message:`One of ${x.join(", ")} is required.`}}}for(let b of _.params)for(let p of[i,s,o]){if(p[b.name]===void 0)continue;if(b.type==="boolean"&&typeof p[b.name]=="string")p[b.name]=p[b.name]==="true";else if((b.type==="integer"||b.type==="number")&&typeof p[b.name]=="string"){let S=Number(p[b.name]);if(!Number.isFinite(S))return{status:400,data:{error:"INVALID_VALUE",code:400,message:`Invalid value for ${b.name}: "${p[b.name]}". Expected a number.`}};p[b.name]=S}if(b.enum&&!b.enum.includes(String(p[b.name])))return{status:400,data:{error:"INVALID_VALUE",code:400,message:`Invalid value for ${b.name}: "${p[b.name]}". Expected one of: ${b.enum.join(", ")}`}};let f=b.pattern?Sr.get(b.pattern):void 0,E=b.format?He[b.format]:void 0,x=f??E;if(x&&typeof p[b.name]=="string"){let S=p[b.name],Y=b.format?je[b.format]:void 0;if(!(x.test(S)&&(!f||!E||E.test(S))&&(!Y||Y(S)))){let W=(b.description??"").split(/\.\s/)[0].replace(/\.?\s*$/,""),kt=W?`. ${W}.`:".";return{status:400,data:{error:"INVALID_VALUE",code:400,message:`Invalid value for ${b.name}: "${String(S).slice(0,80)}"${kt}`}}}}}}if(!Ke(c)){let b={...o,...i,...s};try{let p=await c(b,r),f=ue[t];return f&&(p=ft(p,f)),{status:200,data:p}}catch(p){if(p instanceof bt)return{status:p.status,data:p.data};let f=p instanceof R?p.status:500,E=R.stripGrpcPrefix(p instanceof Error?p.message:String(p));return{status:f,data:{error:"HANDLER_ERROR",code:f,message:E}}}}let{endpoint:l}=c,I={...o,...i},O=ce(I,c.requestMap),{resolvedPath:T,remainingParams:h}=_i(l.path,O);if(T.includes("{"))return{status:400,data:{error:"MISSING_PATH_PARAMS",code:400,message:`Missing required param(s): ${(T.match(/\{([^}]+)\}/g)?.map(p=>p.slice(1,-1))??[]).join(", ")}`}};let N=l.method==="POST"||l.method==="PUT"||l.method==="DELETE"||l.method==="PATCH",v,G=d??null,Z={...ce(s,c.requestMap)};N?(v=`${r.baseURL}${T}`,!G&&Object.keys(h).length>0&&(G=new TextEncoder().encode(JSON.stringify(h)))):(Object.assign(Z,h),v=`${r.baseURL}${T}`);let tt=ci(Z);tt&&(v+=tt);let V=await r.doRequest({method:l.method,url:v,body:G,headers:Object.keys(a).length>0?a:void 0}),ht=new TextDecoder().decode(V.body),A;try{A=JSON.parse(ht)}catch{A=ht}if(V.status>=400)return{status:V.status,data:A,headers:V.headers};c.responseUnwrap&&(A=ui(A,c.responseUnwrap)),c.responseMap&&(A=Ar(A,c.responseMap));let Pt=di(t,c.responseUnwrap);return Pt&&(A=ft(A,Pt)),{status:V.status,data:A,headers:V.headers}}M();u();g();import{createSign as ki,createPrivateKey as Ri,sign as vi,randomBytes as Ti}from"node:crypto";u();g();vt();M();import{readFileSync as pi,writeFileSync as li,mkdirSync as mi}from"node:fs";import{join as wr}from"node:path";var Nr;Nr="0.0.1";var F=Nr,Bt=Pe,yi=`${Rt}/${encodeURIComponent(Bt)}`,Gr="version-check.json";function Cr(t){let e=t.replace(/^v/,"").split("-")[0],[r,n,i]=e.split(".").map(Number);return{major:r??0,minor:n??0,patch:i??0}}function bi(t,e){let r=Cr(t),n=Cr(e);return n.major!==r.major?n.major>r.major:n.minor!==r.minor?n.minor>r.minor:n.patch>r.patch}var fi="https://registry.npmjs.org";function Ei(t,e){let r=Rt===fi?"":` --registry ${Rt}`;return`
|
|
73
|
+
Update available: ${t} \u2192 ${e}
|
|
74
|
+
Run: npm i -g ${Bt}${r}
|
|
75
|
+
`}var Ii=1440*60*1e3;function Dr(){return H()}function Oi(){try{return JSON.parse(pi(wr(Dr(),Gr),"utf8"))}catch{return null}}function hi(t){try{let e=Dr();mi(e,{recursive:!0}),li(wr(e,Gr),JSON.stringify(t))}catch{}}async function Pi(){try{let t=new AbortController,e=setTimeout(()=>t.abort(),3e3),r=await fetch(yi,{signal:t.signal,headers:{Accept:"application/json"}});return clearTimeout(e),r.ok?(await r.json())?.["dist-tags"]?.latest??null:null}catch{return null}}async function Lr(){if(process.env.CI||process.env.COINBASE_NO_UPDATE_CHECK)return"";let t=Oi();if(t&&Date.now()-t.checkedAt<Ii)return"";let e=await Pi();return e?(hi({checkedAt:Date.now(),latestVersion:e}),bi(F,e)?Ei(F,e):""):""}function ge(t){return Buffer.from(t).toString("base64url")}function Ft(t){return ge(Buffer.from(JSON.stringify(t)))}function Ai(){return Ti(16).toString("hex")}function Ur(t){return t.includes("-----BEGIN EC PRIVATE KEY-----")}function qr(t){let e=Buffer.from(t,"base64");return e.length===32||e.length===64}function xi(t,e,r,n,i){let s=Math.floor(Date.now()/1e3),o=Ai(),a={sub:t,iss:"cdp",nbf:s,iat:s,exp:s+120},d=`${r} ${n}${i}`;if(r&&n&&i&&(a.uris=[d]),Ur(e))return Ci(t,e,a,o);if(qr(e))return wi(t,e,a,o);throw new Error("invalid key format - must be either PEM EC key or base64 Ed25519 key")}function Si(t,e,r){let n=`${Ft(t)}.${Ft(e)}`,i=ki("SHA256");i.update(n);let s=i.sign(r);return`${n}.${ge(Ni(s))}`}function Ci(t,e,r,n){return Si({alg:"ES256",typ:"JWT",kid:t,nonce:n},r,e)}function wi(t,e,r,n){let s=`${Ft({alg:"EdDSA",typ:"JWT",kid:t,nonce:n})}.${Ft(r)}`,o=Buffer.from(e,"base64"),a=Ri({key:Buffer.concat([Buffer.from("302e020100300506032b657004220420","hex"),o.subarray(0,32)]),format:"der",type:"pkcs8"}),d=vi(null,Buffer.from(s),a);return`${s}.${ge(d)}`}function Ni(t){let e=2;if(t[e]!==2)throw new Error("invalid DER signature");e++;let r=t[e];e++;let n=t.subarray(e,e+r);if(e+=r,t[e]!==2)throw new Error("invalid DER signature");e++;let i=t[e];e++;let s=t.subarray(e,e+i);return n=Mr(n,32),s=Mr(s,32),Buffer.concat([n,s])}function Mr(t,e){if(t.length===e)return Buffer.from(t);if(t.length>e)return Buffer.from(t.subarray(t.length-e));let r=Buffer.alloc(e);return r.set(t,e-t.length),r}function Gi(t){return Ur(t)?"ecdsa":qr(t)?"ed25519":"unknown"}async function zt(t){let e;if(t.keyId&&t.keySecret){let o=new URL(t.url);e=`Bearer ${xi(t.keyId,t.keySecret,t.method,o.host,o.pathname)}`}else throw new Error("doAuthenticatedRequest: must supply keyId+keySecret");let r={Authorization:e,"Content-Type":"application/json","Correlation-Context":`sdk_version=${encodeURIComponent(F)},sdk_language=typescript,source=${encodeURIComponent(Bt)}`,...t.headers},n=await fetch(t.url,{method:t.method,headers:r,body:t.body?Buffer.from(t.body):void 0}),s={body:new Uint8Array(await n.arrayBuffer()),status:n.status,headers:n.headers};return n.status===401&&t.keySecret&&Gi(t.keySecret)==="ed25519"&&(s.hint="CDP key is Ed25519 but Coinbase Advanced Trade requires ECDSA. Create an ECDSA key at https://portal.cdp.coinbase.com/projects/api-keys (set Signature algorithm to ECDSA at key creation), then `coinbase env <name> --key-file ...`."),s}u();g();M();function k(t,e=!1){return{content:[{type:"text",text:t}],isError:e}}function Br(t,e){return t===e?`coinbase_${t.replace(/-/g,"_")}`:`coinbase_${t.replace(/-/g,"_")}_${e.replace(/[-\s]/g,"_")}`}function Fr(t){let e=[{name:"coinbase_env",description:"Show the active environment and configured environments.",inputSchema:{type:"object",properties:{}}},{name:"coinbase_set_env",description:"Switch the active environment.",inputSchema:{type:"object",properties:{name:{type:"string",description:"Environment name"}},required:["name"]}},{name:"coinbase_template",description:"Get a full request body example for a command, ready to copy and modify.",inputSchema:{type:"object",properties:{resource:{type:"string",description:'Resource name (e.g. "orders"). Omit for top-level actions like balance, transfer, fees.'},action:{type:"string",description:'Action name (e.g. "create", "balance")'}},required:["action"]}},{name:"coinbase_help",description:"Get detailed help for a command: field descriptions, query parameters, and usage.",inputSchema:{type:"object",properties:{resource:{type:"string",description:'Resource name (e.g. "orders"). Omit for top-level actions like balance, transfer, fees.'},action:{type:"string",description:'Action name (e.g. "create", "balance")'}},required:["action"]}}];for(let r of t)for(let n of r.actions){let i={},s=[];for(let c of n.params){let _={type:c.type,description:c.description};c.enum&&(_.enum=c.enum),c.pattern&&(_.pattern=c.pattern),c.format&&(_.format=c.format),i[c.name]=_,c.required&&s.push(c.name)}let o=n.params.filter(c=>c.in==="body").length,a=n.params.filter(c=>c.in==="body"&&c.required).length,d=o>0?a>0?` (${o} fields, ${a} required)`:` (${o} fields)`:"";e.push({name:Br(r.name,n.name),description:`${n.method} ${n.path}${n.summary?" \u2014 "+n.summary:""}${d}`,inputSchema:{type:"object",properties:i,required:s.length>0?s:void 0}})}return e}function zr(t){if(t.status>=400){let e=t.data?.message??JSON.stringify(t.data).slice(0,200),r=t.data?.correlationId,n=t.headers?.get("trace-id"),i=[n?`trace ${n}`:"",r?`correlation ${r}`:""].filter(Boolean).join(", ");return k(`HTTP ${t.status}: ${e}${i?` [${i}]`:""}`,!0)}if(t.data&&typeof t.data=="object"&&"error"in t.data&&typeof t.data.error=="string"){let e=t.data.message?R.stripGrpcPrefix(t.data.message):t.data.error;return k(`Error: ${e}`,!0)}return k(JSON.stringify(t.data,null,2))}function de(t){let e=at(t,""),r=Object.keys(t.environments??{}),n=[`Active: ${e||"(none)"}`,`Configured: ${r.length>0?r.join(", "):"(none)"}`];return e||n.push(`
|
|
76
|
+
Run: coinbase env <name> --key-id=... --key-secret=... to configure.`),k(n.join(`
|
|
77
|
+
`))}function Vr(t,e){if(!e)return k("Environment name is required.",!0);let r=Object.keys(t.environments??{});return r.length===0?k("No environments configured. Run: coinbase env <name> --key-id=... --key-secret=... to configure.",!0):t.environments[e]?{cfg:{...t,env:e}}:k(`Unknown environment: "${e}". Available environments: ${r.join(", ")}`,!0)}function Wr(t){return de(t)}function Di(t,e){let r=t.params??[],n=[];n.push(`${t.summary||t.name}`),n.push(`${t.method} ${t.path}`);let i=t.pathParams.map(a=>`${a}="..."`).join(", ");n.push("",`Usage: ${e}(${i})`);let s=r.filter(a=>a.in==="body");if(s.length>0){n.push("","Fields:");for(let a of s){let d=a.required?" (required)":"",c=a.description?` \u2014 ${a.description}`:"";n.push(` ${a.name} (${a.type})${d}${c}`)}}let o=r.filter(a=>a.in==="query");if(o.length>0){n.push("","Query parameters:");for(let a of o){let d=a.description?` \u2014 ${a.description}`:"";n.push(` ${a.name} (${a.type})${d}`)}}return k(n.join(`
|
|
78
|
+
`))}function $r(t,e,r,n){if(n&&Object.hasOwn(n,e)&&(!t||t===e))return{act:n[e],toolName:`coinbase_${e}`};if(t){let o=r.find(a=>a.name===t);if(o){let a=o.actions.find(d=>d.name===e);return a?{act:a,toolName:Br(o.name,a.name)}:k(`Unknown action: ${e}. Available for ${t}: ${o.actions.map(d=>d.name).join(", ")}`,!0)}}let i=r.map(o=>o.name),s=n?Object.keys(n):[];return k(`Not found. Resources: ${i.join(", ")}. Top-level actions: ${s.join(", ")}`,!0)}function Hr(t,e,r,n){if(!e)return k("action is required",!0);let i=$r(t,e,r,n);return"isError"in i?i:Di(i.act,i.toolName)}function Li(t){let e=(t.params??[]).filter(i=>i.in==="body");if(e.length===0)return k("This action has no request body.");let r=t.template??{},n=[];for(let i of e){let s=i.required?" (required)":"",o=i.description?` \u2014 ${i.description}`:"";n.push(`${i.name} (${i.type})${s}${o}`)}return k(`Template:
|
|
79
|
+
${JSON.stringify(r,null,2)}
|
|
80
|
+
|
|
81
|
+
Fields:
|
|
82
|
+
${n.join(`
|
|
83
|
+
`)}`)}function jr(t,e,r,n){if(!e)return k("action is required",!0);let i=$r(t,e,r,n);return"isError"in i?i:Li(i.act)}function Yr(t){process.stdout.write(JSON.stringify({jsonrpc:"2.0",...t})+`
|
|
84
|
+
`)}function Et(t,e){Yr({id:t,result:e})}function It(t,e,r){Yr({id:t,error:{code:e,message:r}})}function Ui(){return j}async function qi(t,e){let r=Ui();if(t==="coinbase_env")return de(w());if(t==="coinbase_set_env"){let n=w(),i=Vr(n,e.name);return"isError"in i?i:(q(i.cfg),Wr(i.cfg))}return t==="coinbase_help"?Hr(String(e.resource??""),String(e.action??""),r,K):t==="coinbase_template"?jr(String(e.resource??""),String(e.action??""),r,K):Bi(t,e)}async function Bi(t,e){let r=t.replace(/^coinbase_/,"");if(!Ut(r))return k(`Unknown tool: ${t}`,!0);let n=w(),i;try{i=mt(n,"")}catch(a){return k(`Environment error: ${a.message}
|
|
85
|
+
|
|
86
|
+
Run: coinbase env <name> --key-id=... --key-secret=...`,!0)}let{name:s,env:o}=i;try{let a=await lt(s,o),d={envName:s,env:o,baseURL:U(o,s),doRequest:_=>zt({..._,...a})},c=await qt(r,e,d);return zr(c)}catch(a){return k(`Request failed: ${a.message}`,!0)}}var Fi={name:"coinbase",version:F},zi={tools:{}},pe=null;function Kr(){if(!pe){let t=[...j];for(let[e,r]of Object.entries(K))t.push({name:e,actions:[r]});pe=Fr(t)}return pe}async function Vi(t){let{id:e,method:r,params:n}=t;switch(r){case"initialize":Et(e,{protocolVersion:"2024-11-05",serverInfo:Fi,capabilities:zi});break;case"notifications/initialized":break;case"tools/list":Et(e,{tools:Kr()});break;case"tools/call":{let{name:i,arguments:s}=n??{};if(!i){It(e,-32602,"Missing tool name");return}if(!Kr().find(a=>a.name===i)){It(e,-32602,`Unknown tool: ${i}`);return}try{let a=await qi(i,s??{});Et(e,a)}catch(a){Et(e,k(`Internal error: ${a.message}`,!0))}break}case"ping":Et(e,{});break;default:e!=null&&It(e,-32601,`Method not found: ${r}`);break}}function Qr(){let t=Mi({input:process.stdin,terminal:!1}),e=new Set;t.on("line",r=>{if(r.trim())try{let n=JSON.parse(r),i=Vi(n).catch(s=>{process.stderr.write(`MCP error: ${s.message}
|
|
87
|
+
`),n.id!=null&&It(n.id,-32603,s.message)}).finally(()=>e.delete(i));e.add(i)}catch{It(null,-32700,"Parse error")}}),t.on("close",async()=>{await Promise.all(e),process.exit(0)}),process.stderr.write(`coinbase MCP server v${F} ready
|
|
88
|
+
`)}u();g();M();function Xr(t){let e=w();if(t.length===0)return Wi(e);if(t[0]==="clear"){let r=t[1],n;if(r&&r.includes(".")){let i=r.indexOf(".");n=yt(e,r.slice(0,i),r.slice(i+1))}else r?n=yt(e,r):n=yt(e);q(e),process.stderr.write(`Cleared ${n} history ${n===1?"entry":"entries"}.
|
|
89
|
+
`);return}throw new Error("usage: coinbase history [clear [env.action]]")}function Wi(t){let e=[];for(let[i,s]of Object.entries(t.history))for(let[o,a]of Object.entries(s))e.push({env:i,action:o,lastUsed:a.last_used,fields:Object.keys(a.values)});if(e.length===0){process.stderr.write(`No saved history.
|
|
90
|
+
`);return}e.sort((i,s)=>s.lastUsed.localeCompare(i.lastUsed));let r=Math.max(...e.map(i=>i.env.length))+2,n=Math.max(...e.map(i=>i.action.length))+2;for(let i of e){let s=i.lastUsed.slice(0,10);process.stdout.write(`${i.env.padEnd(r)}${i.action.padEnd(n)}${s} ${i.fields.join(", ")}
|
|
91
|
+
`)}}u();g();M();import{readFileSync as rn,writeFileSync as $i,mkdirSync as nn,readdirSync as sn,existsSync as J,cpSync as Hi,rmSync as ji}from"node:fs";import Jr,{join as z,resolve as Zr}from"node:path";import{fileURLToPath as Ki}from"node:url";import{homedir as tn}from"node:os";import{createInterface as en}from"node:readline";function me(){try{return JSON.parse(rn(L(),"utf8"))}catch{return{}}}function le(t){let e=H();nn(e,{recursive:!0,mode:448}),$i(L(),JSON.stringify(t,null,2)+`
|
|
92
|
+
`,{mode:384})}function on(){let t=Jr.dirname(Ki(import.meta.url));return Jr.join(t,"..","skills")}function Yi(t){let e=t.match(/^---\n([\s\S]*?)\n---/);if(!e)return{name:"",description:""};let r=e[1],n=r.match(/^name:\s*(.+)$/m),i=r.match(/^description:\s*(.+)$/m);return{name:(n?.[1]??"").trim(),description:(i?.[1]??"").trim()}}function an(t){let e=t??on();if(!J(e))return[];let r=[];for(let n of sn(e,{withFileTypes:!0})){if(!n.isDirectory())continue;let i=z(e,n.name,"SKILL.md");if(!J(i))continue;let s=rn(i,"utf8"),o=Yi(s);r.push({name:o.name||n.name,description:o.description,dir:z(e,n.name)})}return r.sort((n,i)=>n.name.localeCompare(i.name))}function Qi(t,e){if(!J(t))return 0;nn(e,{recursive:!0});let r=0;for(let n of sn(t,{withFileTypes:!0})){if(!n.isDirectory())continue;let i=z(t,n.name,"SKILL.md");if(!J(i))continue;let s=z(e,n.name);Hi(z(t,n.name),s,{recursive:!0,force:!0}),r++}return r}async function Xi(){let t=process.platform==="win32"?z(process.env.USERPROFILE??tn(),".claude","skills"):z(tn(),".claude","skills");process.stderr.write(`Where should skills be installed?
|
|
93
|
+
`),process.stderr.write(`Tip: Not sure where your agent looks for skills? Ask your agent.
|
|
94
|
+
|
|
95
|
+
`),process.stderr.write(` 1) ${t} User \u2014 available across all projects (Recommended)
|
|
96
|
+
`),process.stderr.write(` 2) Other Specify a custom path
|
|
97
|
+
|
|
98
|
+
`);let e=en({input:process.stdin,output:process.stderr}),r=await new Promise(n=>{e.question("? Skills directory [1]: ",i=>{e.close(),n(i.trim())})});if(!r||r==="1")return t;if(r==="2"){let n=en({input:process.stdin,output:process.stderr}),i=await new Promise(s=>{n.question("? Path: ",o=>{n.close(),s(o.trim())})});return i?Zr(i):(process.stderr.write(`No path provided, using default.
|
|
99
|
+
`),t)}return Zr(r)}async function Ji(t){let e=me(),r=t||e.settings?.skillsDir,n=!1;r||(process.stdin.isTTY||(process.stderr.write(`error: no skills directory configured.
|
|
100
|
+
Run: coinbase skills add --dir <path>
|
|
101
|
+
`),process.exit(1)),r=await Xi(),n=!0),(!e.settings?.skillsDir||t&&e.settings.skillsDir!==r)&&(e.settings||(e.settings={}),e.settings.skillsDir=r,le(e));let i=on(),s=Qi(i,r);process.stderr.write(`Installed ${s} skills to ${r}.
|
|
102
|
+
`),n&&process.stderr.write(`Skills will auto-update on future installs.
|
|
103
|
+
`)}function Zi(){let e=me().settings?.skillsDir,r=an();if(r.length===0){process.stderr.write(`No bundled skills found.
|
|
104
|
+
`);return}let n=Math.max(...r.map(i=>i.name.length),4);process.stderr.write(`${"Name".padEnd(n+2)}${"Installed".padEnd(12)}Description
|
|
105
|
+
`),process.stderr.write(`${"\u2500".repeat(n+2)}${"\u2500".repeat(12)}${"\u2500".repeat(40)}
|
|
106
|
+
`);for(let i of r){let o=e&&J(z(e,i.name,"SKILL.md"))?"yes":"no";process.stderr.write(`${i.name.padEnd(n+2)}${o.padEnd(12)}${i.description}
|
|
107
|
+
`)}process.stderr.write(`
|
|
108
|
+
${r.length} skills available.`),e?process.stderr.write(` Installed to: ${e}`):process.stderr.write(" Run: coinbase skills add"),process.stderr.write(`
|
|
109
|
+
`)}function ts(){let t=me(),e=t.settings?.skillsDir;if(!e){process.stderr.write(`No skills directory configured. Nothing to remove.
|
|
110
|
+
`);return}if(!J(e)){process.stderr.write(`Skills directory not found: ${e}
|
|
111
|
+
`),t.settings&&delete t.settings.skillsDir,le(t),process.stderr.write(`Cleared skillsDir from config.
|
|
112
|
+
`);return}let r=an(),n=0;for(let i of r){let s=z(e,i.name);J(s)&&(ji(s,{recursive:!0,force:!0}),n++)}t.settings&&delete t.settings.skillsDir,le(t),process.stderr.write(`Removed ${n} skills from ${e}.
|
|
113
|
+
`),process.stderr.write(`Cleared skillsDir from config.
|
|
114
|
+
`)}async function _n(t){let e=t[0];if(!e||e==="-h"||e==="--help"){process.stderr.write(`Manage CDP agent skills
|
|
115
|
+
|
|
116
|
+
Usage:
|
|
117
|
+
coinbase skills add Install/update skills to your skills directory
|
|
118
|
+
coinbase skills add --dir <path> Install skills to a specific directory
|
|
119
|
+
coinbase skills list List available skills from this package
|
|
120
|
+
coinbase skills remove Remove installed skills and clear config
|
|
121
|
+
|
|
122
|
+
`);return}let r,n=t.slice(1);for(let i=0;i<n.length;i++)if(n[i]==="--dir"&&n[i+1]){r=n[++i];break}else if(n[i].startsWith("--dir=")){r=n[i].slice(6);break}switch(e){case"add":await Ji(r);break;case"list":Zi();break;case"remove":ts();break;default:process.stderr.write(`error: unknown skills subcommand: ${e}
|
|
123
|
+
Run: coinbase skills --help
|
|
124
|
+
`),process.exit(1)}}u();g();u();g();u();g();function es(t,e){return t.FORCE_HYPERLINK==="0"?!1:t.FORCE_HYPERLINK?!0:!e||t.CI?!1:!!(/ghostty|iterm|wezterm|hyper/i.test(t.TERM_PROGRAM||"")||t.WT_SESSION||parseInt(t.VTE_VERSION||"",10)>=5e3)}var cn=es(process.env,!!process.stdout.isTTY);function ye(t){return t.replace(/\x1b\]8;;[^\x07]*\x07/g,"").replace(/\x1b\[[0-9;]*m/g,"").length}function rs(t,e,r){return r?`\x1B]8;;${e}\x07\x1B[4m${t}\x1B[24m\x1B]8;;\x07`:`${t} (${e})`}function un(t,e=cn){return t.replace(/\[([^\]]+)\]\(([^)]+)\)/g,(r,n,i)=>rs(n,i,e))}function be(t){return t.replace(/\*\*(.+?)\*\*/g,"$1").replace(/__(.+?)__/g,"$1").replace(/\*(.+?)\*/g,"$1").replace(/_(.+?)_/g,"$1").replace(/\[([^\]]+)\]\([^)]+\)/g,"$1").replace(/^#{1,6}\s+/gm,"")}function gn(t,e){let r=e??process.stdout.columns??100;return t.replace(/\r\n/g,`
|
|
125
|
+
`).trim().split(`
|
|
126
|
+
`).flatMap(n=>{n=be(n).replace(/^\s*\*\s+/," - ").replace(/^\s*(\d+)\.\s+/," $1. ");let i=[];for(;n.length>r;){let s=n.lastIndexOf(" ",r);s<=0&&(s=r),i.push(n.slice(0,s)),n=n.slice(s+1)}return i.push(n),i}).join(`
|
|
127
|
+
`)+`
|
|
128
|
+
`}u();g();function Vt(t,e,r,n){let i=n??e.length,s=Math.max(20,(process.stdout.columns||80)-i),o=r.split(" "),a="",d=!0;for(let c of o){let _=ye(c),l=ye(a),I=c.includes("\x1B]8;;");(l+_+1>s||I&&l>0&&l+_+1>s*.6)&&a.length>0&&(t.write((d?e:" ".repeat(i))+a.trimEnd()+`
|
|
129
|
+
`),a="",d=!1),a+=(a?" ":"")+c}a&&t.write((d?e:" ".repeat(i))+a.trimEnd()+`
|
|
130
|
+
`)}function fe(){return j}function dn(t){return $e.has(t)}function Ee(t,e){let r=process.stderr;r.write(`${e.summary||e.name}
|
|
131
|
+
`),e.description&&r.write(`
|
|
132
|
+
`+gn(e.description));let n=t===e.name?`coinbase ${e.name}`:`coinbase ${t} ${e.name}`;for(let c of e.pathParams)n+=` <${c}>`;r.write(`
|
|
133
|
+
Usage:
|
|
134
|
+
${n} [fields...]
|
|
135
|
+
`);let i=e.params??[],s=i.filter(c=>c.in==="path");if(s.length>0){r.write(`
|
|
136
|
+
Arguments:
|
|
137
|
+
`);let c=Math.max(...s.map(_=>_.name.length));for(let _ of s){let l=` <${_.name}>${" ".repeat(Math.max(1,c-_.name.length+2))}${_.type}${_.required?" required":""} `;Vt(r,l,_.description)}}let o=i.filter(c=>c.in==="body");if(o.length>0){r.write(`
|
|
138
|
+
Fields:
|
|
139
|
+
`);let c=Math.max(...o.map(_=>_.name.length));for(let _ of o){let l=_.required?"required":"",I=" ".repeat(Math.max(1,c-_.name.length+2)),O=" ".repeat(Math.max(1,8-_.type.length)),T=l?" ":" ",h=_.type==="array"||_.type==="object"?" (use :=json)":"",N=un(be(_.description)),v=` ${_.name}${I}${_.type}${O}${l}${T}`;Vt(r,v,N+h)}}let a=i.filter(c=>c.in==="query");if(a.length>0){r.write(`
|
|
140
|
+
Query params (use == syntax):
|
|
141
|
+
`);let c=Math.max(...a.map(_=>_.name.length));for(let _ of a){let l=_.description;_.enum?.length&&(l+=(l?" ":"")+`[${_.enum.join(", ")}]`);let I=` ${_.name}${" ".repeat(Math.max(1,c-_.name.length+2))}${_.type} `;Vt(r,I,l)}}let d=o.length>0;if(r.write(`
|
|
142
|
+
Flags:
|
|
143
|
+
`),d&&r.write(` --template Show request body template from spec
|
|
144
|
+
`),r.write(` --dry-run Preview request without sending
|
|
145
|
+
`),d&&r.write(` --edit Open request body in $EDITOR
|
|
146
|
+
`),r.write(` --jq <expr> Filter response with jq
|
|
147
|
+
`),e.streamable&&r.write(` --paginate Auto-paginate using nextPageToken
|
|
148
|
+
`),d||a.length>0){r.write(`
|
|
149
|
+
Example:
|
|
150
|
+
`+n);for(let c of o)c.example!==void 0&&r.write(` ${c.name}=${c.example}`);for(let c of a)c.example!==void 0&&r.write(` ${c.name}==${c.example}`);r.write(`
|
|
151
|
+
`)}}function pn(t){let e=j.find(r=>r.name===t);e||(process.stderr.write(`error: unknown resource: ${t}
|
|
152
|
+
Run: coinbase --help
|
|
153
|
+
`),process.exit(1)),process.stderr.write(`Manage ${e.name}
|
|
154
|
+
|
|
155
|
+
Available actions:
|
|
156
|
+
`);for(let r of e.actions){let n=r.pathParams.map(i=>` <${i}>`).join("");process.stderr.write(` coinbase ${e.name} ${r.name}${n} ${r.summary}
|
|
157
|
+
`)}process.stderr.write(`
|
|
158
|
+
Flags:
|
|
159
|
+
--template Show request body template from spec
|
|
160
|
+
--dry-run Show assembled request without sending
|
|
161
|
+
--edit Open request body in $EDITOR
|
|
162
|
+
--jq <expr> jq filter expression for response
|
|
163
|
+
`)}u();g();u();g();var ln=t=>t?.has_next===!1?null:t?.cursor||null;M();async function Oe(t,e,r,n){if(r.flags.template){let p=e.template??{};process.stdout.write(JSON.stringify(p,null,2)+`
|
|
164
|
+
`);return}let i=[],s=[],{isFieldArg:o}=await Promise.resolve().then(()=>($t(),Wt));for(let p of r.args)i.length<e.pathParams.length&&!o(p)?i.push(p):s.push(p);if(i.length<e.pathParams.length){let p=e.pathParams.slice(i.length);throw new Error(`Missing argument(s): ${p.map(f=>`<${f}>`).join(" ")}
|
|
165
|
+
|
|
166
|
+
Usage: coinbase ${t.replace(/_/g," ")} ${e.pathParams.map(f=>`<${f}>`).join(" ")} [fields...]`)}let{parseFields:a}=await Promise.resolve().then(()=>($t(),Wt)),d=a(s),c=!1,_={},l=w(),I=at(l,n);if(I&&Lt(l)){let p=ne(l,I,t);if(p){let{setNested:f}=await Promise.resolve().then(()=>($t(),Wt));for(let[E,x]of Object.entries(p.values))try{f(_,E,JSON.parse(x))}catch{f(_,E,x)}c=!0}}let O=e.params??[];for(let p of Object.keys(d.body)){let f=O.find(E=>E.name===p);if(f?.oneOf)for(let E of O)E.oneOf===f.oneOf&&E.name!==p&&delete _[E.name]}let h=Object.keys(d.body).length>0?{..._,...d.body}:c?(process.stderr.write(`Using saved values from last ${t.replace(/_/g," ")} call. Pass fields to override.
|
|
167
|
+
`),{..._}):{...d.body},N={};for(let[p,f]of d.query.entries())N[p]=f;let v={};for(let p=0;p<i.length;p++)v[e.pathParams[p]]=i[p];let G=O.filter(p=>p.in==="body"&&p.required&&p.type!=="object"&&p.type!=="array"),ut=G.map(p=>p.name);if(ut.length>0&&Object.keys(h).length===0&&!r.flags.edit&&!d.rawBody)if(process.stdin.isTTY)if(G.length<=3){let{createInterface:p}=await import("node:readline"),f=p({input:process.stdin,output:process.stderr});for(let E of G){let x=E.enum?` [${E.enum.join(", ")}]`:"",S=E.example?` (e.g. ${E.example})`:"",Y=await new Promise(gt=>{f.question(`${E.name}${x}${S}: `,gt)});Y&&(h[E.name]=Y)}f.close()}else process.stderr.write(`This action requires ${ut.length} fields. Opening editor.
|
|
168
|
+
`),r.flags.edit=!0;else{let p=ut.join(", ");throw new Error(`Missing required field(s): ${p}
|
|
169
|
+
|
|
170
|
+
Provide fields inline:
|
|
171
|
+
coinbase ${t.replace(/_/g," ")} ${ut.map(f=>`${f}=<value>`).join(" ")}
|
|
172
|
+
|
|
173
|
+
Or use --template to see the full body:
|
|
174
|
+
coinbase ${t.replace(/_/g," ")} --template
|
|
175
|
+
`)}if(r.flags.edit){let f={...e.template??{},...h},E=await fs(JSON.stringify(f,null,2));for(let x of Object.keys(h))delete h[x];Object.assign(h,E)}if(r.flags["dry-run"]){process.stderr.write(`would execute ${t}
|
|
176
|
+
`);let p={...v,...h,...Object.keys(N).length>0?{_query:N}:{}};process.stdout.write(JSON.stringify(p,null,2)+`
|
|
177
|
+
`);return}let{name:Z,env:tt}=mt(l,n),V=await lt(Z,tt),ht={envName:Z,env:tt,baseURL:U(tt,Z),doRequest:p=>zt({...p,...V})},A={body:h,query:{...N},path:v,headers:d.headers,rawBody:d.rawBody??void 0},{outputResult:Pt}=await Promise.resolve().then(()=>(fn(),bn)),b=0;do{let p=await qt(t,A,ht);if(Pt(p,r.flags.jq),p.status<400&&Object.keys(h).length>0)try{if(I&&Lt(l)){let E={};(function x(S,Y){for(let[gt,W]of Object.entries(Y)){let kt=S?`${S}.${gt}`:gt;typeof W=="object"&&W&&!Array.isArray(W)?x(kt,W):E[kt]=JSON.stringify(W)}})("",h),re(l,I,t,E),q(l)}}catch{}if(!r.flags.paginate)break;let f=ln(p.data);if(!f||f===A.query.cursor)break;if(A.query.cursor=f,++b>=100){process.stderr.write(`Pagination limit reached (100 pages)
|
|
178
|
+
`);break}}while(!0)}function ys(){if(process.platform!=="win32")return"vi";try{let{execSync:t}=Pn("node:child_process");return t("where code",{stdio:"pipe"}),"code --wait"}catch{}return"notepad"}function bs(t){return t.split(`
|
|
179
|
+
`).filter(e=>!e.trimStart().startsWith("//")).join(`
|
|
180
|
+
`)}async function fs(t){let{writeFileSync:e,readFileSync:r,unlinkSync:n,mkdtempSync:i}=await import("node:fs"),{tmpdir:s}=await import("node:os"),{join:o}=await import("node:path"),{execFileSync:a}=await import("node:child_process"),{scrubbedEnv:d}=await Promise.resolve().then(()=>(M(),Ve));if(!process.stdin.isTTY)throw new Error("--edit requires an interactive terminal. Use --template to get the request body, then pass fields as arguments.");let c=process.env.VISUAL??process.env.EDITOR??ys();(c.trim()==="code"||c.trim().endsWith("/code"))&&(c+=" --wait");let _=o(i(o(s(),"coinbase-")),"request.json");e(_,t);try{let[l,...I]=c.split(/\s+/);a(l,[...I,_],{stdio:"inherit",env:d()});let O=bs(r(_,"utf8"));return JSON.parse(O)}catch(l){throw new Error(l.message?.includes("Unexpected")?`Edited file is not valid JSON: ${l.message}`:`Editor: ${l.message}`)}finally{try{n(_)}catch{}}}u();g();var Ot=class extends Error{constructor(e){super(e),this.name="HelpExit"}};function En(t){let e={children:new Map};for(let r of t){let n=e;for(let i of r.name.split(" "))n.children.has(i)||n.children.set(i,{children:new Map}),n=n.children.get(i);n.action=r}return e}function In(t,e,r){let n=t,i,s=0,o=[],a=[];for(let l=0;l<e.length;l++){let I=e[l],O=n.children.get(I);if(O){a.push(I),o=[...n.children.keys()],n=O,n.action&&(i=n.action,s=a.length);continue}if(I.includes("=")){let T=I.split("=")[0],h=n.children.get(T);if(h?.action){let N=h.action.pathParams.map(G=>` <${G}>`).join(""),v=a.length>0?`coinbase ${r} ${a.join(" ")}`:`coinbase ${r}`;throw new Error(`Did you mean: ${v} ${h.action.name.split(" ").pop()}${N}`)}}break}if(i)return{action:i,remaining:e.slice(s),siblingNames:o};let d=e[a.length],c=a.length>0?`coinbase ${r} ${a.join(" ")}`:`coinbase ${r}`,_=Es(n,r);throw d&&d!=="--help"&&d!=="-h"?new Error(`unknown subcommand "${d}" for ${c}
|
|
181
|
+
|
|
182
|
+
Available actions:
|
|
183
|
+
${_}`):new Ot(`${c}
|
|
184
|
+
|
|
185
|
+
Available actions:
|
|
186
|
+
${_}`)}function Es(t,e){let r=[];function n(i){if(i.action){let s=i.action.pathParams.map(o=>` <${o}>`).join("");r.push(` coinbase ${e} ${i.action.name}${s}`)}for(let s of i.children.values())n(s)}return n(t),r.join(`
|
|
187
|
+
`)}vt();u();g();function ct(t,e){let r={},n=[],i=!1,s=new Map;for(let[o,a,d]of e)s.set("--"+o,[o,d]),a&&s.set("-"+a,[o,d]);for(let o=0;o<t.length;o++){let a=t[o];if(a==="-h"||a==="--help"){i=!0;continue}let d=a.indexOf("=");if(d>0&&a.startsWith("--")){let _=s.get(a.slice(0,d));if(_){r[_[0]]=a.slice(d+1);continue}}let c=s.get(a);if(c){r[c[0]]=c[1]?t[++o]??"":!0;continue}if(a.length>2&&a[0]==="-"&&a[1]!=="-"){let _=s.get(a.slice(0,2));if(_?.[1]){r[_[0]]=a.slice(2);continue}}n.push(a)}return{flags:r,args:n,help:i}}process.stdout.on("error",t=>{throw t.code==="EPIPE"&&process.exit(0),t});process.stderr.on("error",t=>{throw t.code==="EPIPE"&&process.exit(0),t});var[Is]=process.versions.node.split(".").map(Number);Is<22&&(process.stderr.write(`coinbase requires Node.js ${22}+. You have: ${process.versions.node}
|
|
188
|
+
`),process.exit(1));function Os(){return`Coinbase CLI
|
|
189
|
+
|
|
190
|
+
Usage:
|
|
191
|
+
coinbase <resource> <action> [fields...]
|
|
192
|
+
coinbase <action> [fields...]
|
|
193
|
+
|
|
194
|
+
Resources:
|
|
195
|
+
${fe().map(r=>` ${r.name.padEnd(20)}${r.actions.map(n=>n.name).join(", ")}`).join(`
|
|
196
|
+
`)}
|
|
197
|
+
|
|
198
|
+
Actions:
|
|
199
|
+
balance View balances
|
|
200
|
+
transfer Move funds between portfolios
|
|
201
|
+
|
|
202
|
+
Info:
|
|
203
|
+
fees Fee tier and 30-day volume
|
|
204
|
+
history Request history
|
|
205
|
+
|
|
206
|
+
Session:
|
|
207
|
+
env Manage credentials
|
|
208
|
+
mcp MCP server (stdio)
|
|
209
|
+
|
|
210
|
+
Flags:
|
|
211
|
+
-e, --env <name> Environment override
|
|
212
|
+
-V, --version Show version
|
|
213
|
+
-h, --help Show help
|
|
214
|
+
`}async function hs(){let t=process.argv.slice(2),e="",r=[];for(let s=0;s<t.length;s++){let o=t[s];o==="-e"||o==="--env"?e=t[++s]??"":o.startsWith("--env=")?e=o.slice(6):o.startsWith("-e")&&o.length>2?e=o.slice(2):r.push(o)}if(r[0]==="--version"||r[0]==="-V"){process.stdout.write(`coinbase ${F}
|
|
215
|
+
`);return}if(he=Lr(),r.length===0||r[0]==="-h"||r[0]==="--help"||r[0]==="help"){process.stderr.write(Os());return}let[n,...i]=r;if(n==="env"){let s=ct(i,[["key-id",null,!0],["key-secret",null,!0],["key-file","k",!0],["status",null,!1],["url",null,!0],["remove",null,!1],["allow-plaintext-secrets",null,!1]]);if(s.help){process.stderr.write(`Manage environments and credentials
|
|
216
|
+
|
|
217
|
+
Usage:
|
|
218
|
+
coinbase env List all environments
|
|
219
|
+
coinbase env <name> Switch to or set up an environment
|
|
220
|
+
coinbase env <name> --key-file key.json Import from CDP key file
|
|
221
|
+
coinbase env <name> --key-id=... --key-secret=... Configure inline (CDP key)
|
|
222
|
+
coinbase env <name> --status Show credential status
|
|
223
|
+
coinbase env <name> --remove Remove an environment
|
|
224
|
+
|
|
225
|
+
Flags:
|
|
226
|
+
--status Show credential status for the env
|
|
227
|
+
-k, --key-file <f> Import from CDP API key JSON file
|
|
228
|
+
--key-id <id> API key ID
|
|
229
|
+
--key-secret <sec> API key secret
|
|
230
|
+
--url <url> Custom base URL
|
|
231
|
+
--remove Remove an environment
|
|
232
|
+
--allow-plaintext-secrets Persist secrets to ~/.config/coinbase/config.json (mode 0600) when no OS keychain is available. Default: refuse and error.
|
|
233
|
+
`);return}await We(s.args,{keyId:s.flags["key-id"],keySecret:s.flags["key-secret"],keyFile:s.flags["key-file"],status:!!s.flags.status,url:s.flags.url,remove:!!s.flags.remove,allowPlaintextSecrets:!!s.flags["allow-plaintext-secrets"],envFlag:e})}else if(n==="history"){let s=ct(i,[]);if(s.help){process.stderr.write(`View or clear saved request history
|
|
234
|
+
|
|
235
|
+
Usage:
|
|
236
|
+
coinbase history Show all saved history
|
|
237
|
+
coinbase history clear Clear all history
|
|
238
|
+
coinbase history clear <env> Clear history for an environment
|
|
239
|
+
coinbase history clear <env>.<action> Clear a specific entry
|
|
240
|
+
`);return}Xr(s.args)}else if(n==="skills")await _n(i);else if(n==="mcp"){if(ct(i,[]).help){process.stderr.write(`MCP server for AI agent integration
|
|
241
|
+
|
|
242
|
+
Usage:
|
|
243
|
+
coinbase mcp Start MCP server over stdio
|
|
244
|
+
|
|
245
|
+
The MCP server implements the Model Context Protocol (JSON-RPC over
|
|
246
|
+
stdin/stdout) and exposes all brokerage API resources as tools. It is
|
|
247
|
+
typically invoked automatically by AI agents, not run directly.
|
|
248
|
+
`);return}Qr()}else if(dn(n)){let s=K[n];s||(process.stderr.write(`error: "${n}" is not available
|
|
249
|
+
`),process.exit(1));let o=ct(i,[["template",null,!1],["dry-run",null,!1],["edit",null,!1],["jq",null,!0],["paginate",null,!1]]);if(o.help){Ee(n,s);return}await Oe(n,s,o,e)}else{let s=fe().find(_=>_.name===n);if(s||(process.stderr.write(`error: unknown command "${n}"
|
|
250
|
+
Run: coinbase --help
|
|
251
|
+
`),process.exit(1)),i.length===0||i[0]==="-h"||i[0]==="--help"){pn(n);return}let o=En(s.actions),a=In(o,i,n),d=ct(a.remaining,[["template",null,!1],["dry-run",null,!1],["edit",null,!1],["jq",null,!0],["paginate",null,!1]]);if(d.help){Ee(n,a.action);return}let c=a.action.id??`${n}_${a.action.name.replace(/[-\s]/g,"_")}`;await Oe(c,a.action,d,e)}}var he;hs().catch(t=>{t instanceof Ot&&(process.stderr.write(t.message+`
|
|
252
|
+
`),process.exit(0)),process.stderr.write(`error: ${t.message}
|
|
253
|
+
`),process.exit(1)}).finally(async()=>{if(!he||!process.stderr.isTTY)return;let t=await he.catch(()=>"");t&&process.stderr.write(t)});
|
package/dist/version.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.1
|
package/package.json
CHANGED
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coinbase/coinbase-cli",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Coinbase for agents — CLI and MCP server for trading, wallets, payments, and onchain across the Coinbase platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"coinbase": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "BUILD=external node scripts/embed-spec.mjs",
|
|
11
|
+
"gen:types": "BUNDLE_SKIP=1 BUILD=external node scripts/embed-spec.mjs && node scripts/gen-types.mjs specs/cli/coinbase-cli.yaml '' > generated/cli/types.ts && node scripts/gen-types.mjs dist/spec.json '' RetailBrokerageApi_ > generated/brokerage/types.ts && node scripts/gen-client.mjs > generated/brokerage/client.ts && node scripts/gen-surface.mjs specs/cli/coinbase-cli.yaml > generated/cli/surface.ts",
|
|
12
|
+
"gen:test": "node scripts/gen-mapping-tests.mjs",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"test": "node --experimental-strip-types --no-warnings --test 'src/**/*.test.ts'",
|
|
15
|
+
"test:keyring": "node --experimental-strip-types --no-warnings --test src/lib/keyring/keyring.integration-test.ts",
|
|
16
|
+
"postinstall": "node scripts/postinstall.mjs"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=22"
|
|
20
|
+
},
|
|
21
|
+
"packageManager": "pnpm@10.11.1",
|
|
22
|
+
"files": [
|
|
23
|
+
"dist/",
|
|
24
|
+
"skills/",
|
|
25
|
+
"scripts/postinstall.mjs"
|
|
26
|
+
],
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^25.2.3",
|
|
29
|
+
"esbuild": "^0.27.3",
|
|
30
|
+
"swagger2openapi": "^7.0.8",
|
|
31
|
+
"typescript": "^5.7.0",
|
|
32
|
+
"yaml": "^2.6.0"
|
|
33
|
+
},
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"homepage": "https://docs.cdp.coinbase.com",
|
|
36
|
+
"keywords": [
|
|
37
|
+
"coinbase",
|
|
38
|
+
"cli",
|
|
39
|
+
"mcp",
|
|
40
|
+
"api",
|
|
41
|
+
"agents",
|
|
42
|
+
"crypto",
|
|
43
|
+
"trading"
|
|
44
|
+
]
|
|
8
45
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// postinstall — auto-update skills if skillsDir is configured.
|
|
3
|
+
// NEVER exits non-zero to avoid breaking npm install.
|
|
4
|
+
|
|
5
|
+
import { readFileSync, readdirSync, existsSync, mkdirSync, cpSync } from 'node:fs';
|
|
6
|
+
import { join, dirname } from 'node:path';
|
|
7
|
+
import { homedir } from 'node:os';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
function configPath() {
|
|
11
|
+
if (process.env.COINBASE_CONFIG_DIR) return join(process.env.COINBASE_CONFIG_DIR, 'config.json');
|
|
12
|
+
if (process.platform === 'win32') {
|
|
13
|
+
const appData = process.env.APPDATA ?? join(homedir(), 'AppData', 'Roaming');
|
|
14
|
+
return join(appData, 'coinbase', 'config.json');
|
|
15
|
+
}
|
|
16
|
+
return join(homedir(), '.config', 'coinbase', 'config.json');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function copySkills(srcDir, destDir) {
|
|
20
|
+
if (!existsSync(srcDir)) return 0;
|
|
21
|
+
mkdirSync(destDir, { recursive: true });
|
|
22
|
+
let count = 0;
|
|
23
|
+
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
24
|
+
if (!entry.isDirectory()) continue;
|
|
25
|
+
const skillMd = join(srcDir, entry.name, 'SKILL.md');
|
|
26
|
+
if (!existsSync(skillMd)) continue;
|
|
27
|
+
cpSync(join(srcDir, entry.name), join(destDir, entry.name), { recursive: true, force: true });
|
|
28
|
+
count++;
|
|
29
|
+
}
|
|
30
|
+
return count;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const cfgFile = configPath();
|
|
35
|
+
if (!existsSync(cfgFile)) process.exit(0);
|
|
36
|
+
|
|
37
|
+
const cfg = JSON.parse(readFileSync(cfgFile, 'utf8'));
|
|
38
|
+
const skillsDir = cfg?.settings?.skillsDir;
|
|
39
|
+
if (!skillsDir) process.exit(0);
|
|
40
|
+
|
|
41
|
+
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
42
|
+
const packageRoot = dirname(thisDir);
|
|
43
|
+
const srcDir = join(packageRoot, 'skills');
|
|
44
|
+
|
|
45
|
+
if (!existsSync(srcDir)) process.exit(0);
|
|
46
|
+
|
|
47
|
+
const count = copySkills(srcDir, skillsDir);
|
|
48
|
+
process.stderr.write(`coinbase: updated ${count} skills in ${skillsDir}\n`);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
process.stderr.write(`coinbase: warning: could not update skills: ${err.message}\n`);
|
|
51
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: coinbase
|
|
3
|
+
description: Use this skill when the user wants to trade cryptocurrency on Coinbase Advanced Trade — check prices, place market or limit orders, manage portfolios, view balances, convert currencies, or query brokerage data. Execute commands with the `coinbase` binary.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# coinbase CLI
|
|
7
|
+
|
|
8
|
+
`coinbase` is an agent-first CLI for the Coinbase Advanced Trade brokerage API. All commands output JSON to stdout by default. Errors are JSON to stderr with non-zero exit codes.
|
|
9
|
+
|
|
10
|
+
## Prerequisites
|
|
11
|
+
|
|
12
|
+
Authenticated commands need a CDP API key configured per environment.
|
|
13
|
+
|
|
14
|
+
1. Go to https://portal.cdp.coinbase.com/projects/api-keys → Click **Create API Key**
|
|
15
|
+
2. In the Create API Key modal:
|
|
16
|
+
- Under **API restrictions → Coinbase App & Advanced Trade**:
|
|
17
|
+
- Select the **Portfolio** this key will be scoped to (e.g. "Primary"). The key can only trade within this portfolio.
|
|
18
|
+
- Enable **Trade** and **Transfer** permissions (View is on by default).
|
|
19
|
+
- Under **Advanced Settings** (bottom of modal):
|
|
20
|
+
- Select **ECDSA** signature algorithm. The brokerage SDK rejects Ed25519.
|
|
21
|
+
3. Download the key file and configure:
|
|
22
|
+
```sh
|
|
23
|
+
coinbase env live --key-file path/to/cdp_api_key.json
|
|
24
|
+
```
|
|
25
|
+
4. Verify: `coinbase balance`
|
|
26
|
+
|
|
27
|
+
### Multiple portfolios
|
|
28
|
+
|
|
29
|
+
Each CDP key is scoped to one portfolio. To operate across portfolios, create a key per portfolio and register each as a separate environment using the `live-<portfolio>` convention:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
coinbase env live-primary --key-file primary-key.json
|
|
33
|
+
coinbase env live-trading --key-file trading-key.json
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Switch with `coinbase env live-trading` or per-command with `-e live-primary`. Moving funds between portfolios requires switching to the source portfolio's key first.
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Discovery
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
coinbase --help # All resources + commands
|
|
43
|
+
coinbase orders --help # Actions in a resource
|
|
44
|
+
coinbase orders create --template # Request body template
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Common workflows
|
|
48
|
+
|
|
49
|
+
### Check a price (authenticated)
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
coinbase products get BTC-USD # Current BTC product info
|
|
53
|
+
coinbase products candles BTC-USD --jq '.candles[0]'
|
|
54
|
+
coinbase products book BTC-USD --jq '.pricebook'
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### List balances
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
coinbase balance # All account balances
|
|
61
|
+
coinbase balance show_zero==true # Include zero-balance accounts
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Funding a fresh account
|
|
65
|
+
|
|
66
|
+
The brokerage API does not support buying crypto with a payment method directly. To fund an account for trading:
|
|
67
|
+
|
|
68
|
+
1. Deposit USD or crypto via [coinbase.com](https://www.coinbase.com) or the Coinbase mobile app. Deposits land in the **default portfolio**.
|
|
69
|
+
2. If trading from a non-default portfolio, transfer funds from default using:
|
|
70
|
+
```sh
|
|
71
|
+
coinbase transfer \
|
|
72
|
+
amount=100 currency=USD \
|
|
73
|
+
from=<default_portfolio_uuid> \
|
|
74
|
+
to=<trading_portfolio_uuid>
|
|
75
|
+
```
|
|
76
|
+
3. Verify: `coinbase balance`
|
|
77
|
+
|
|
78
|
+
### Place a market order
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
# Buy $100 of BTC at market
|
|
82
|
+
coinbase orders create \
|
|
83
|
+
client_order_id=$(uuidgen) \
|
|
84
|
+
product_id=BTC-USD \
|
|
85
|
+
side=BUY \
|
|
86
|
+
type=market \
|
|
87
|
+
quote_size=100
|
|
88
|
+
|
|
89
|
+
# Sell 0.5 ETH at market
|
|
90
|
+
coinbase orders create \
|
|
91
|
+
client_order_id=$(uuidgen) \
|
|
92
|
+
product_id=ETH-USD \
|
|
93
|
+
side=SELL \
|
|
94
|
+
type=market \
|
|
95
|
+
base_size=0.5
|
|
96
|
+
|
|
97
|
+
# Preview before sending
|
|
98
|
+
coinbase orders preview \
|
|
99
|
+
product_id=BTC-USD \
|
|
100
|
+
side=BUY \
|
|
101
|
+
type=market \
|
|
102
|
+
quote_size=100
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
SELL market orders use `base_size` (amount of asset to sell). BUY market orders use `quote_size` (amount of USD to spend).
|
|
106
|
+
|
|
107
|
+
### Manage orders
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
coinbase orders list order_status==OPEN
|
|
111
|
+
coinbase orders get <order_id>
|
|
112
|
+
coinbase orders cancel order_ids:='["id-1","id-2"]'
|
|
113
|
+
coinbase orders edit <order_id> price=50000
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Portfolios
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
coinbase portfolios list
|
|
120
|
+
coinbase portfolios get <portfolio_uuid>
|
|
121
|
+
coinbase portfolios create name="Day Trading"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Convert (USDC ↔ USD, etc.)
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
coinbase convert quote from=USD to=USDC amount=100
|
|
128
|
+
coinbase convert execute <trade_id> from=USD to=USDC
|
|
129
|
+
coinbase convert get <trade_id>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Transfer funds between portfolios
|
|
133
|
+
|
|
134
|
+
```sh
|
|
135
|
+
coinbase transfer amount=1000 currency=USD from=<src_uuid> to=<dst_uuid>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Field syntax
|
|
139
|
+
|
|
140
|
+
| Syntax | Meaning |
|
|
141
|
+
| :---- | :---- |
|
|
142
|
+
| `key=value` | String body field |
|
|
143
|
+
| `key:=value` | Raw JSON body field |
|
|
144
|
+
| `key==value` | Query parameter |
|
|
145
|
+
| `a.b.c=value` | Nested body field |
|
|
146
|
+
| `@file.json` | Body from file |
|
|
147
|
+
| `-` | Body from stdin |
|
|
148
|
+
|
|
149
|
+
## Flags
|
|
150
|
+
|
|
151
|
+
| Flag | Behavior |
|
|
152
|
+
| :---- | :---- |
|
|
153
|
+
| `--template` | Print request body schema, don't send |
|
|
154
|
+
| `--dry-run` | Assemble + print request, don't send |
|
|
155
|
+
| `--edit` | Open body in `$EDITOR` |
|
|
156
|
+
| `--jq <expr>` | Filter JSON response |
|
|
157
|
+
| `--paginate` | Auto-follow `cursor` until exhausted |
|
|
158
|
+
| `-e <env>` | Override active environment |
|
|
159
|
+
| `-v` | Verbose; print headers (sensitive redacted) |
|
|
160
|
+
|
|
161
|
+
## Output
|
|
162
|
+
|
|
163
|
+
- `stdout`: raw JSON response (default), parseable.
|
|
164
|
+
- `stderr`: human status, errors, warnings.
|
|
165
|
+
- Exit codes: `0` success, `1` error.
|
|
166
|
+
|
|
167
|
+
## Portfolio ID for spot orders (CDP-key auth)
|
|
168
|
+
|
|
169
|
+
CDP API keys derive portfolio scope from the key itself — no `retail_portfolio_id` needed in the body.
|
|
170
|
+
|
|
171
|
+
## MCP
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
claude mcp add --scope user --transport stdio coinbase -- coinbase mcp
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
29 tools generated from the spec: `coinbase_<resource>_<action>` (e.g. `coinbase_orders_create`) and top-level actions (e.g. `coinbase_balance`, `coinbase_transfer`, `coinbase_fees`), plus meta tools `coinbase_env`, `coinbase_set_env`, `coinbase_template`, `coinbase_help`.
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
console.log("Temporary package");
|