o2-cli 0.1.0__py3-none-any.whl

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.
o2_cli/setup.py ADDED
@@ -0,0 +1,561 @@
1
+ """O2 CLI Setup Wizard - Install skill files for different vibe coding tools.
2
+
3
+ Supports:
4
+ - Claude Code → ~/.claude/skills/ or .claude/skills/
5
+ - Cursor → .cursor/rules/
6
+ - Codex (OpenAI) → AGENTS.md
7
+ - Windsurf → .windsurfrules
8
+ - Cline → .clinerules
9
+ - Trae → .trae/rules/
10
+
11
+ Usage:
12
+ o2 setup # Interactive wizard
13
+ o2 setup --tool claude-code --scope global # Non-interactive
14
+ o2 setup --update # Re-install all configured tools
15
+ """
16
+
17
+ import json
18
+ import subprocess
19
+ import sys
20
+ from pathlib import Path
21
+ from typing import Optional
22
+
23
+ from rich.console import Console
24
+ from rich.panel import Panel
25
+ from rich.table import Table
26
+
27
+ from o2_cli import __version__
28
+
29
+ console = Console()
30
+
31
+ # ── Skill content (single source of truth) ──────────────────────────────────
32
+
33
+ SKILL_CONTENT = """\
34
+ # O2 CLI Trading Tool
35
+
36
+ **Category**: Trading
37
+ **Severity**: Normal
38
+ **Auto-trigger**: Yes
39
+
40
+ ---
41
+
42
+ ## When to Use
43
+
44
+ 当用户提到以下操作时,使用 O2 CLI 而不是直接调用 API:
45
+
46
+ - 查询 O2 余额、订单、持仓、市场数据
47
+ - 创建/取消/修改订单
48
+ - 充值/提币操作
49
+ - 账户设置(杠杆、保证金模式)
50
+ - 查看 K 线、订单簿、手续费
51
+
52
+ ---
53
+
54
+ ## Quick Reference
55
+
56
+ **安装**(首次使用前检查):
57
+
58
+ ```bash
59
+ which o2 || pip install o2-cli
60
+ ```
61
+
62
+ ### 核心规则
63
+
64
+ 1. **`--json` 必须放在命令前面**: `o2 --json balance show` / `o2 balance show --json` ❌
65
+ 2. **公开命令无需登录**: `markets list`, `fees rates`
66
+ 3. **其他命令需要先登录**: `o2 auth test-login`
67
+ 4. **退出码**: 0=成功, 1=失败(错误到 stderr,数据到 stdout)
68
+
69
+ ### 常用命令
70
+
71
+ ```bash
72
+ # 认证
73
+ o2 auth test-login # 登录(token 自动保存)
74
+
75
+ # 市场数据(公开)
76
+ o2 --json markets list # 市场列表
77
+ o2 --json markets orderbook --market-id 1 # BTC 订单簿
78
+ o2 --json markets candles --market-id 1 --interval 1h
79
+
80
+ # 余额
81
+ o2 --json balance show # 余额(现金+赠金)
82
+ o2 --json balance history
83
+
84
+ # 订单
85
+ o2 --json orders create -m 1 -s long -t market -a 0.001 # 市价做多
86
+ o2 --json orders create -m 1 -s short -t limit -a 0.001 -p 85000 # 限价做空
87
+ o2 --json orders list --status open
88
+ o2 --json orders cancel --order-id <ID>
89
+ o2 --json orders cancel-all
90
+
91
+ # 持仓
92
+ o2 --json positions list
93
+ o2 --json positions close --position-id <ID>
94
+ o2 --json positions risk --market-id 1
95
+
96
+ # 设置
97
+ o2 --json settings leverage --market-id 1 --leverage 10
98
+ o2 --json settings margin-mode --mode cross
99
+
100
+ # 充提
101
+ o2 --json deposits address --chain base
102
+ o2 --json withdrawals create --amount 500 --address 0x... --chain ethereum
103
+
104
+ # 其他
105
+ o2 --json trades list
106
+ o2 --json fees rates
107
+ o2 --json account overview
108
+ o2 --json notifications list
109
+ ```
110
+
111
+ ### 参数说明
112
+
113
+ | 参数 | 含义 | 值 |
114
+ |------|------|-----|
115
+ | `--market-id` / `-m` | 市场 | 1=BTC, 2=ETH |
116
+ | `--side` / `-s` | 方向 | `long` / `short` |
117
+ | `--order-type` / `-t` | 类型 | `market` / `limit` |
118
+ | `--base-amount` / `-a` | 数量 | 实际数量如 0.001 |
119
+ | `--price` / `-p` | 价格 | USDC |
120
+ | `--leverage` / `-l` | 杠杆 | 1-50 |
121
+
122
+ ### 故障排查
123
+
124
+ - `Cannot connect` → 启动 O2 Backend
125
+ - `Not authenticated` → `o2 auth test-login`
126
+ - `o2: command not found` → `pip install o2-cli`
127
+
128
+ ### 更新
129
+
130
+ ```bash
131
+ o2 setup --update # 更新所有已安装工具的 skill 文件
132
+ ```
133
+ """
134
+
135
+ # Cursor MDC 格式(带 frontmatter)
136
+ CURSOR_MDC_CONTENT = """\
137
+ ---
138
+ description: O2 CLI Trading Tool - 使用命令行操作 O2 交易平台
139
+ globs:
140
+ alwaysApply: true
141
+ ---
142
+
143
+ """ + SKILL_CONTENT
144
+
145
+ # Codex AGENTS.md 区块
146
+ CODEX_SECTION = """
147
+
148
+ ## O2 CLI Trading Tool
149
+
150
+ 使用 O2 CLI 操作 O2 交易平台。安装: `pip install o2-cli`
151
+
152
+ ### 核心规则
153
+ 1. `--json` 放在命令前面: `o2 --json balance show`
154
+ 2. 公开命令无需登录: `markets list`, `fees rates`
155
+ 3. 需要登录的命令先执行: `o2 auth test-login`
156
+
157
+ ### 常用命令
158
+ ```bash
159
+ o2 auth test-login # 登录
160
+ o2 --json markets list # 市场列表
161
+ o2 --json balance show # 余额
162
+ o2 --json orders create -m 1 -s long -t market -a 0.001 # 市价做多
163
+ o2 --json orders list --status open # 查询订单
164
+ o2 --json positions list # 持仓
165
+ o2 --json settings leverage --market-id 1 --leverage 10 # 杠杆
166
+ o2 --json deposits address --chain base # 充值地址
167
+ o2 --json account overview # 账户总览
168
+ ```
169
+
170
+ 详细文档: `o2 setup --show-skill`
171
+ """
172
+
173
+ # Windsurf / Cline 规则格式(追加到文件末尾)
174
+ RULES_SECTION = """
175
+
176
+ # O2 CLI Trading Tool
177
+
178
+ 使用 O2 CLI 操作 O2 交易平台。安装: `pip install o2-cli`
179
+
180
+ ## 规则
181
+ 1. `--json` 放命令前面: `o2 --json balance show` (不是 `o2 balance show --json`)
182
+ 2. 公开命令无需登录: `markets list`, `fees rates`
183
+ 3. 需要登录先执行: `o2 auth test-login`
184
+
185
+ ## 常用命令
186
+ - `o2 --json markets list` - 市场列表
187
+ - `o2 --json balance show` - 余额
188
+ - `o2 --json orders create -m 1 -s long -t market -a 0.001` - 市价做多
189
+ - `o2 --json orders list --status open` - 查询订单
190
+ - `o2 --json positions list` - 持仓
191
+ - `o2 --json account overview` - 账户总览
192
+
193
+ 详细文档: `o2 setup --show-skill`
194
+ """
195
+
196
+
197
+ # ── Tool definitions ────────────────────────────────────────────────────────
198
+
199
+ class ToolConfig:
200
+ """Defines how to install skill for a specific vibe coding tool."""
201
+
202
+ def __init__(
203
+ self,
204
+ name: str,
205
+ display_name: str,
206
+ install_scopes: list[str], # ["global", "project"] or ["project"]
207
+ ):
208
+ self.name = name
209
+ self.display_name = display_name
210
+ self.install_scopes = install_scopes
211
+
212
+ def get_install_path(self, scope: str, project_dir: Path) -> Path:
213
+ raise NotImplementedError
214
+
215
+ def get_content(self) -> str:
216
+ raise NotImplementedError
217
+
218
+ def is_installed(self, scope: str, project_dir: Path) -> bool:
219
+ return self.get_install_path(scope, project_dir).exists()
220
+
221
+ def install(self, scope: str, project_dir: Path) -> Path:
222
+ path = self.get_install_path(scope, project_dir)
223
+ path.parent.mkdir(parents=True, exist_ok=True)
224
+ content = self.get_content()
225
+
226
+ if self.name == "codex":
227
+ # Append to AGENTS.md, don't overwrite
228
+ if path.exists():
229
+ existing = path.read_text(encoding="utf-8")
230
+ if "O2 CLI Trading Tool" in existing:
231
+ # Replace existing section
232
+ import re
233
+ pattern = r"\n## O2 CLI Trading Tool\n.*"
234
+ existing = re.sub(pattern, "", existing, flags=re.DOTALL)
235
+ path.write_text(existing.rstrip() + "\n" + content, encoding="utf-8")
236
+ else:
237
+ path.write_text(content.lstrip(), encoding="utf-8")
238
+ elif self.name in ("windsurf", "cline"):
239
+ # Append to rules file
240
+ if path.exists():
241
+ existing = path.read_text(encoding="utf-8")
242
+ if "O2 CLI Trading Tool" in existing:
243
+ import re
244
+ pattern = r"\n# O2 CLI Trading Tool\n.*"
245
+ existing = re.sub(pattern, "", existing, flags=re.DOTALL)
246
+ path.write_text(existing.rstrip() + "\n" + content, encoding="utf-8")
247
+ else:
248
+ path.write_text(content.lstrip(), encoding="utf-8")
249
+ else:
250
+ path.write_text(content, encoding="utf-8")
251
+
252
+ return path
253
+
254
+
255
+ class ClaudeCodeTool(ToolConfig):
256
+ def __init__(self):
257
+ super().__init__("claude-code", "Claude Code", ["global", "project"])
258
+
259
+ def get_install_path(self, scope: str, project_dir: Path) -> Path:
260
+ if scope == "global":
261
+ return Path.home() / ".claude" / "skills" / "o2-cli" / "SKILL.md"
262
+ return project_dir / ".claude" / "skills" / "o2-cli" / "SKILL.md"
263
+
264
+ def get_content(self) -> str:
265
+ return SKILL_CONTENT
266
+
267
+
268
+ class CursorTool(ToolConfig):
269
+ def __init__(self):
270
+ super().__init__("cursor", "Cursor", ["project"])
271
+
272
+ def get_install_path(self, scope: str, project_dir: Path) -> Path:
273
+ return project_dir / ".cursor" / "rules" / "o2-cli.mdc"
274
+
275
+ def get_content(self) -> str:
276
+ return CURSOR_MDC_CONTENT
277
+
278
+
279
+ class CodexTool(ToolConfig):
280
+ def __init__(self):
281
+ super().__init__("codex", "Codex (OpenAI)", ["project"])
282
+
283
+ def get_install_path(self, scope: str, project_dir: Path) -> Path:
284
+ return project_dir / "AGENTS.md"
285
+
286
+ def get_content(self) -> str:
287
+ return CODEX_SECTION
288
+
289
+
290
+ class WindsurfTool(ToolConfig):
291
+ def __init__(self):
292
+ super().__init__("windsurf", "Windsurf", ["project"])
293
+
294
+ def get_install_path(self, scope: str, project_dir: Path) -> Path:
295
+ return project_dir / ".windsurfrules"
296
+
297
+ def get_content(self) -> str:
298
+ return RULES_SECTION
299
+
300
+
301
+ class ClineTool(ToolConfig):
302
+ def __init__(self):
303
+ super().__init__("cline", "Cline (VS Code)", ["project"])
304
+
305
+ def get_install_path(self, scope: str, project_dir: Path) -> Path:
306
+ return project_dir / ".clinerules"
307
+
308
+ def get_content(self) -> str:
309
+ return RULES_SECTION
310
+
311
+
312
+ class TraeTool(ToolConfig):
313
+ def __init__(self):
314
+ super().__init__("trae", "Trae", ["project"])
315
+
316
+ def get_install_path(self, scope: str, project_dir: Path) -> Path:
317
+ return project_dir / ".trae" / "rules" / "o2-cli.md"
318
+
319
+ def get_content(self) -> str:
320
+ return SKILL_CONTENT
321
+
322
+
323
+ ALL_TOOLS: list[ToolConfig] = [
324
+ ClaudeCodeTool(),
325
+ CursorTool(),
326
+ CodexTool(),
327
+ WindsurfTool(),
328
+ ClineTool(),
329
+ TraeTool(),
330
+ ]
331
+
332
+ TOOL_BY_NAME: dict[str, ToolConfig] = {t.name: t for t in ALL_TOOLS}
333
+
334
+ # ── Install state tracking ──────────────────────────────────────────────────
335
+
336
+ STATE_FILE = Path.home() / ".o2" / "setup-state.json"
337
+
338
+
339
+ def load_state() -> dict:
340
+ """Load setup state (which tools are installed, version, etc.)."""
341
+ if STATE_FILE.exists():
342
+ try:
343
+ return json.loads(STATE_FILE.read_text(encoding="utf-8"))
344
+ except (json.JSONDecodeError, OSError):
345
+ pass
346
+ return {"installed_tools": [], "version": None}
347
+
348
+
349
+ def save_state(state: dict) -> None:
350
+ """Save setup state."""
351
+ STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
352
+ state["version"] = __version__
353
+ STATE_FILE.write_text(json.dumps(state, indent=2, ensure_ascii=False), encoding="utf-8")
354
+
355
+
356
+ # ── CLI check ───────────────────────────────────────────────────────────────
357
+
358
+ def is_cli_installed() -> bool:
359
+ """Check if o2 CLI is in PATH."""
360
+ try:
361
+ result = subprocess.run(
362
+ ["which", "o2"], capture_output=True, text=True, timeout=5
363
+ )
364
+ return result.returncode == 0
365
+ except Exception:
366
+ return False
367
+
368
+
369
+ # ── Interactive setup ───────────────────────────────────────────────────────
370
+
371
+ def interactive_setup(project_dir: Path | None = None) -> None:
372
+ """Run interactive setup wizard."""
373
+ if project_dir is None:
374
+ project_dir = Path.cwd()
375
+
376
+ console.print(Panel(
377
+ "[bold]O2 CLI Setup Wizard[/bold]\n\n"
378
+ "Select your vibe coding tool(s) to install the skill file.\n"
379
+ "Run again anytime to add more tools or switch tools.",
380
+ title="o2 setup",
381
+ border_style="cyan",
382
+ ))
383
+
384
+ # Step 1: Check CLI
385
+ if is_cli_installed():
386
+ console.print("[green]✓[/green] o2 CLI is installed")
387
+ else:
388
+ console.print("[yellow]⚠[/yellow] o2 CLI not in PATH")
389
+ console.print(" Install with: [bold]pip install -e .[/bold]")
390
+ if not confirm("Continue setup anyway?"):
391
+ return
392
+
393
+ # Step 2: Show installed tools
394
+ state = load_state()
395
+ if state.get("installed_tools"):
396
+ console.print(f"\n[dim]Previously installed: {', '.join(state['installed_tools'])}[/dim]")
397
+
398
+ # Step 3: Select tools
399
+ console.print("\n[bold]Available tools:[/bold]")
400
+ for i, tool in enumerate(ALL_TOOLS, 1):
401
+ scopes = "/".join(tool.install_scopes)
402
+ console.print(f" {i}. {tool.display_name} ({scopes})")
403
+ console.print(f" 0. [bold]All tools[/bold]")
404
+
405
+ choice = input("\nSelect tool number (or comma-separated, e.g. 1,3): ").strip()
406
+
407
+ if choice == "0":
408
+ selected_tools = ALL_TOOLS
409
+ else:
410
+ selected_tools = []
411
+ for num in choice.split(","):
412
+ try:
413
+ idx = int(num.strip()) - 1
414
+ if 0 <= idx < len(ALL_TOOLS):
415
+ selected_tools.append(ALL_TOOLS[idx])
416
+ except ValueError:
417
+ console.print(f"[red]Invalid: {num}[/red]")
418
+
419
+ if not selected_tools:
420
+ console.print("[red]No tools selected.[/red]")
421
+ return
422
+
423
+ # Step 4: For each tool, choose scope and install
424
+ installed = []
425
+ for tool in selected_tools:
426
+ console.print(f"\n[bold cyan]→ {tool.display_name}[/bold cyan]")
427
+
428
+ # Choose scope
429
+ if len(tool.install_scopes) > 1:
430
+ scope = choose_scope(tool.install_scopes)
431
+ else:
432
+ scope = tool.install_scopes[0]
433
+ console.print(f" Scope: {scope}")
434
+
435
+ # Install
436
+ path = tool.install(scope, project_dir)
437
+ console.print(f" [green]✓[/green] Installed to: [dim]{path}[/dim]")
438
+ installed.append({"tool": tool.name, "scope": scope, "path": str(path)})
439
+
440
+ # Step 5: Save state
441
+ state["installed_tools"] = [item["tool"] for item in installed]
442
+ state["install_details"] = installed
443
+ save_state(state)
444
+
445
+ console.print(Panel(
446
+ f"[bold green]Setup complete![/bold green]\n\n"
447
+ f"Installed {len(installed)} tool(s):\n"
448
+ + "\n".join(f" • {item['tool']} ({item['scope']}) → {item['path']}" for item in installed)
449
+ + f"\n\nTo update skills later: [bold]o2 setup --update[/bold]"
450
+ + f"\nTo change tools: [bold]o2 setup[/bold] (re-run)",
451
+ border_style="green",
452
+ ))
453
+
454
+
455
+ def choose_scope(scopes: list[str]) -> str:
456
+ """Let user choose install scope."""
457
+ if len(scopes) == 1:
458
+ return scopes[0]
459
+
460
+ console.print(" Choose scope:")
461
+ for i, s in enumerate(scopes, 1):
462
+ desc = "system-wide (~/.claude/skills/)" if s == "global" else "project-level (./.claude/skills/)"
463
+ console.print(f" {i}. {s} ({desc})")
464
+
465
+ while True:
466
+ try:
467
+ choice = int(input(" Scope [1]: ").strip() or "1")
468
+ if 1 <= choice <= len(scopes):
469
+ return scopes[choice - 1]
470
+ except ValueError:
471
+ pass
472
+ console.print(" [red]Invalid choice[/red]")
473
+
474
+
475
+ def confirm(message: str) -> bool:
476
+ return input(f"{message} [y/N]: ").strip().lower() in ("y", "yes")
477
+
478
+
479
+ # ── Non-interactive setup ───────────────────────────────────────────────────
480
+
481
+ def setup_tool(tool_name: str, scope: str = "project", project_dir: Path | None = None) -> None:
482
+ """Non-interactive setup for CI/agent use."""
483
+ if project_dir is None:
484
+ project_dir = Path.cwd()
485
+
486
+ tool = TOOL_BY_NAME.get(tool_name)
487
+ if not tool:
488
+ console.print(f"[red]Unknown tool: {tool_name}[/red]")
489
+ console.print(f"Available: {', '.join(TOOL_BY_NAME.keys())}")
490
+ return
491
+
492
+ path = tool.install(scope, project_dir)
493
+ state = load_state()
494
+ if tool_name not in state.get("installed_tools", []):
495
+ state.setdefault("installed_tools", []).append(tool_name)
496
+ save_state(state)
497
+ console.print(f"[green]✓[/green] {tool.display_name} installed to: {path}")
498
+
499
+
500
+ # ── Update ──────────────────────────────────────────────────────────────────
501
+
502
+ def update_skills(project_dir: Path | None = None) -> None:
503
+ """Re-install skill files for all previously configured tools."""
504
+ if project_dir is None:
505
+ project_dir = Path.cwd()
506
+
507
+ state = load_state()
508
+ installed = state.get("installed_tools", [])
509
+
510
+ if not installed:
511
+ console.print("[yellow]No tools configured. Run [bold]o2 setup[/bold] first.[/yellow]")
512
+ return
513
+
514
+ console.print(f"[bold]Updating skills for: {', '.join(installed)}[/bold]")
515
+ for tool_name in installed:
516
+ tool = TOOL_BY_NAME.get(tool_name)
517
+ if not tool:
518
+ continue
519
+ # Find the scope from install details
520
+ details = state.get("install_details", [])
521
+ scope = "project"
522
+ for d in details:
523
+ if d.get("tool") == tool_name:
524
+ scope = d.get("scope", "project")
525
+ break
526
+ path = tool.install(scope, project_dir)
527
+ console.print(f" [green]✓[/green] {tool.display_name} → {path}")
528
+
529
+ state["version"] = __version__
530
+ save_state(state)
531
+ console.print("[green]All skills updated![/green]")
532
+
533
+
534
+ def show_skill() -> None:
535
+ """Print the full skill content to stdout."""
536
+ print(SKILL_CONTENT)
537
+
538
+
539
+ def show_status() -> None:
540
+ """Show current setup status."""
541
+ state = load_state()
542
+ installed = state.get("installed_tools", [])
543
+ version = state.get("version", "unknown")
544
+
545
+ table = Table(title="O2 CLI Setup Status")
546
+ table.add_column("Item", style="bold")
547
+ table.add_column("Value")
548
+
549
+ table.add_row("CLI Version", __version__)
550
+ table.add_row("Installed in PATH", "Yes" if is_cli_installed() else "No")
551
+ table.add_row("Skills Version", version)
552
+ table.add_row("Configured Tools", ", ".join(installed) if installed else "None")
553
+
554
+ console.print(table)
555
+
556
+ if installed:
557
+ console.print("\n[bold]Install details:[/bold]")
558
+ for d in state.get("install_details", []):
559
+ tool = TOOL_BY_NAME.get(d.get("tool", ""))
560
+ name = tool.display_name if tool else d["tool"]
561
+ console.print(f" • {name} ({d.get('scope', '?')}) → {d.get('path', '?')}")
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.4
2
+ Name: o2-cli
3
+ Version: 0.1.0
4
+ Summary: CLI for O2 DEX Trading Platform
5
+ Author: Dylan Wu
6
+ License-Expression: MIT
7
+ Keywords: cli,trading,dex,lighter,o2
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Office/Business :: Financial :: Investment
16
+ Requires-Python: >=3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: typer[all]>=0.9.0
20
+ Requires-Dist: rich>=13.0.0
21
+ Requires-Dist: httpx>=0.25.0
22
+ Requires-Dist: pyyaml>=6.0
23
+ Requires-Dist: prompt-toolkit>=3.0.0
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.4.0; extra == "dev"
26
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
27
+ Requires-Dist: respx>=0.20.0; extra == "dev"
28
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # O2 CLI
32
+
33
+ Command-line interface for [O2 DEX](https://github.com/dylanwu19850222/lighter-dex) Trading Platform.
34
+
35
+ ## Install
36
+
37
+ ```bash
38
+ pip install o2-cli
39
+ ```
40
+
41
+ ## Quick Start
42
+
43
+ ```bash
44
+ # Login (dev environment)
45
+ o2 auth test-login
46
+
47
+ # View markets (public, no login needed)
48
+ o2 --json markets list
49
+
50
+ # Check balance
51
+ o2 --json balance show
52
+
53
+ # Place a market order (long 0.001 BTC)
54
+ o2 --json orders create -m 1 -s long -t market -a 0.001
55
+
56
+ # View positions
57
+ o2 --json positions list
58
+ ```
59
+
60
+ ## Vibe Coding Tool Setup
61
+
62
+ After installing, run the setup wizard to install skill files for your AI coding tool:
63
+
64
+ ```bash
65
+ # Interactive wizard
66
+ o2 setup
67
+
68
+ # Non-interactive
69
+ o2 setup --tool claude-code --scope global
70
+ o2 setup --tool cursor --scope project
71
+
72
+ # Update all installed tools
73
+ o2 setup --update
74
+ ```
75
+
76
+ Supported tools: **Claude Code**, **Cursor**, **Codex (OpenAI)**, **Windsurf**, **Cline**, **Trae**
77
+
78
+ ## Key Rules
79
+
80
+ 1. `--json` must come before the subcommand: `o2 --json balance show` (not `o2 balance show --json`)
81
+ 2. Public commands (no login): `markets list`, `fees rates`
82
+ 3. Other commands require `o2 auth test-login` first
83
+ 4. Exit codes: 0 = success, 1 = error
84
+
85
+ ## Commands
86
+
87
+ | Group | Commands | Auth Required |
88
+ |-------|----------|---------------|
89
+ | `auth` | `test-login`, `me`, `session` | No (login) |
90
+ | `markets` | `list`, `orderbook`, `candles`, `trades` | No |
91
+ | `fees` | `rates`, `estimate` | No |
92
+ | `balance` | `show`, `history` | Yes |
93
+ | `orders` | `create`, `list`, `cancel`, `cancel-all`, `modify`, `batch` | Yes |
94
+ | `positions` | `list`, `market`, `close`, `risk` | Yes |
95
+ | `trades` | `list`, `summary` | Yes |
96
+ | `deposits` | `address`, `history` | Yes |
97
+ | `withdrawals` | `create`, `status`, `cancel`, `list` | Yes |
98
+ | `settings` | `get`, `leverage`, `margin-mode` | Yes |
99
+ | `notifications` | `list`, `unread`, `read` | Yes |
100
+ | `account` | `overview` | Yes |
101
+ | `mm` | `status`, `start`, `stop`, `stats`, `orders` | API Key |
102
+ | `admin` | `gas-status`, `proxy-list`, `api-keys`, `reconcile` | Admin JWT |
103
+ | `setup` | wizard, `--tool`, `--update`, `--status` | No |
104
+
105
+ ## Configuration
106
+
107
+ Config file: `~/.o2/config.yaml`
108
+
109
+ ```yaml
110
+ active_profile: default
111
+ profiles:
112
+ default:
113
+ api_url: http://localhost:8000/api/v1
114
+ timeout: 30
115
+ auth_type: jwt
116
+ token: eyJ... # auto-saved
117
+ ```
118
+
119
+ Override at runtime:
120
+ ```bash
121
+ o2 --profile production --json balance show
122
+ o2 --api-url https://api.example.com/api/v1 --json markets list
123
+ ```
124
+
125
+ ## Development
126
+
127
+ ```bash
128
+ git clone https://github.com/dylanwu19850222/o2-cli.git
129
+ cd o2-cli
130
+ pip install -e ".[dev]"
131
+
132
+ # Run tests
133
+ pytest
134
+
135
+ # Lint
136
+ ruff check o2_cli/
137
+ ```
138
+
139
+ ## License
140
+
141
+ MIT