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.
@@ -0,0 +1,158 @@
1
+ """o2 positions commands - Position management."""
2
+
3
+ import asyncio
4
+ from typing import Optional
5
+
6
+ import typer
7
+
8
+ from o2_cli.cli import get_state
9
+ from o2_cli.client import O2Client
10
+ from o2_cli.config import load_config, get_active_profile
11
+ from o2_cli.exceptions import APIError, ConnectionError
12
+ from o2_cli.output import OutputFormatter
13
+
14
+ app = typer.Typer(help="Position management")
15
+
16
+
17
+ @app.command("list")
18
+ def list_positions():
19
+ """List all open positions."""
20
+ asyncio.run(_list_positions())
21
+
22
+
23
+ async def _list_positions():
24
+ state = get_state()
25
+ formatter = OutputFormatter(json_mode=state["json_output"])
26
+ config = load_config()
27
+ profile = get_active_profile(config)
28
+ api_url = state.get("api_url") or profile.api_url
29
+ timeout = state.get("timeout") or profile.timeout
30
+
31
+ if not profile.token and profile.auth_type == "jwt":
32
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
33
+ raise typer.Exit(1)
34
+
35
+ async with O2Client(api_url, timeout) as client:
36
+ client.set_jwt(profile.token)
37
+ if profile.api_key_id:
38
+ client.set_api_key(profile.api_key_id, profile.api_secret)
39
+ try:
40
+ data = await client.get_positions()
41
+ formatter.print_positions(data)
42
+ except APIError as e:
43
+ formatter.print_error(str(e), e.code)
44
+ raise typer.Exit(1)
45
+ except ConnectionError as e:
46
+ formatter.print_error(str(e))
47
+ raise typer.Exit(1)
48
+
49
+
50
+ @app.command("market")
51
+ def market(
52
+ market_id: int = typer.Option(..., "--market-id", "-m", help="Market ID"),
53
+ ):
54
+ """Show position for a specific market."""
55
+ asyncio.run(_market(market_id))
56
+
57
+
58
+ async def _market(market_id: int):
59
+ state = get_state()
60
+ formatter = OutputFormatter(json_mode=state["json_output"])
61
+ config = load_config()
62
+ profile = get_active_profile(config)
63
+ api_url = state.get("api_url") or profile.api_url
64
+ timeout = state.get("timeout") or profile.timeout
65
+
66
+ if not profile.token and profile.auth_type == "jwt":
67
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
68
+ raise typer.Exit(1)
69
+
70
+ async with O2Client(api_url, timeout) as client:
71
+ client.set_jwt(profile.token)
72
+ if profile.api_key_id:
73
+ client.set_api_key(profile.api_key_id, profile.api_secret)
74
+ try:
75
+ data = await client.get_position_by_market(market_id)
76
+ formatter.print_positions(data)
77
+ except APIError as e:
78
+ formatter.print_error(str(e), e.code)
79
+ raise typer.Exit(1)
80
+ except ConnectionError as e:
81
+ formatter.print_error(str(e))
82
+ raise typer.Exit(1)
83
+
84
+
85
+ @app.command("close")
86
+ def close(
87
+ position_id: str = typer.Option(
88
+ ..., "--position-id", "-i", help="Position ID to close"
89
+ ),
90
+ ):
91
+ """Close an open position."""
92
+ asyncio.run(_close(position_id))
93
+
94
+
95
+ async def _close(position_id: str):
96
+ state = get_state()
97
+ formatter = OutputFormatter(json_mode=state["json_output"])
98
+ config = load_config()
99
+ profile = get_active_profile(config)
100
+ api_url = state.get("api_url") or profile.api_url
101
+ timeout = state.get("timeout") or profile.timeout
102
+
103
+ if not profile.token and profile.auth_type == "jwt":
104
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
105
+ raise typer.Exit(1)
106
+
107
+ async with O2Client(api_url, timeout) as client:
108
+ client.set_jwt(profile.token)
109
+ if profile.api_key_id:
110
+ client.set_api_key(profile.api_key_id, profile.api_secret)
111
+ try:
112
+ data = await client.close_position(position_id)
113
+ formatter.print_raw(data)
114
+ if not state["json_output"]:
115
+ formatter.print_success(f"Position {position_id} closed")
116
+ except APIError as e:
117
+ formatter.print_error(str(e), e.code)
118
+ raise typer.Exit(1)
119
+ except ConnectionError as e:
120
+ formatter.print_error(str(e))
121
+ raise typer.Exit(1)
122
+
123
+
124
+ @app.command("risk")
125
+ def risk(
126
+ market_id: int = typer.Option(
127
+ ..., "--market-id", "-m", help="Market ID to check liquidation risk"
128
+ ),
129
+ ):
130
+ """Check liquidation risk for a market position."""
131
+ asyncio.run(_risk(market_id))
132
+
133
+
134
+ async def _risk(market_id: int):
135
+ state = get_state()
136
+ formatter = OutputFormatter(json_mode=state["json_output"])
137
+ config = load_config()
138
+ profile = get_active_profile(config)
139
+ api_url = state.get("api_url") or profile.api_url
140
+ timeout = state.get("timeout") or profile.timeout
141
+
142
+ if not profile.token and profile.auth_type == "jwt":
143
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
144
+ raise typer.Exit(1)
145
+
146
+ async with O2Client(api_url, timeout) as client:
147
+ client.set_jwt(profile.token)
148
+ if profile.api_key_id:
149
+ client.set_api_key(profile.api_key_id, profile.api_secret)
150
+ try:
151
+ data = await client.get_liquidation_risk(market_id)
152
+ formatter.print_raw(data)
153
+ except APIError as e:
154
+ formatter.print_error(str(e), e.code)
155
+ raise typer.Exit(1)
156
+ except ConnectionError as e:
157
+ formatter.print_error(str(e))
158
+ raise typer.Exit(1)
@@ -0,0 +1,129 @@
1
+ """o2 settings commands - User settings management."""
2
+
3
+ import asyncio
4
+
5
+ import typer
6
+
7
+ from o2_cli.cli import get_state
8
+ from o2_cli.client import O2Client
9
+ from o2_cli.config import load_config, get_active_profile
10
+ from o2_cli.exceptions import APIError, ConnectionError
11
+ from o2_cli.output import OutputFormatter
12
+
13
+ app = typer.Typer(help="User settings")
14
+
15
+
16
+ @app.command("get")
17
+ def get():
18
+ """Show current user settings."""
19
+ asyncio.run(_get())
20
+
21
+
22
+ async def _get():
23
+ state = get_state()
24
+ formatter = OutputFormatter(json_mode=state["json_output"])
25
+ config = load_config()
26
+ profile = get_active_profile(config)
27
+ api_url = state.get("api_url") or profile.api_url
28
+ timeout = state.get("timeout") or profile.timeout
29
+
30
+ if not profile.token and profile.auth_type == "jwt":
31
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
32
+ raise typer.Exit(1)
33
+
34
+ async with O2Client(api_url, timeout) as client:
35
+ client.set_jwt(profile.token)
36
+ if profile.api_key_id:
37
+ client.set_api_key(profile.api_key_id, profile.api_secret)
38
+ try:
39
+ data = await client.get_user_settings()
40
+ formatter.print_raw(data)
41
+ except APIError as e:
42
+ formatter.print_error(str(e), e.code)
43
+ raise typer.Exit(1)
44
+ except ConnectionError as e:
45
+ formatter.print_error(str(e))
46
+ raise typer.Exit(1)
47
+
48
+
49
+ @app.command("leverage")
50
+ def leverage(
51
+ market_id: int = typer.Option(..., "--market-id", "-m", help="Market ID"),
52
+ leverage: int = typer.Option(..., "--leverage", "-l", help="Leverage value"),
53
+ ):
54
+ """Set leverage for a market."""
55
+ asyncio.run(_leverage(market_id, leverage))
56
+
57
+
58
+ async def _leverage(market_id: int, leverage_val: int):
59
+ state = get_state()
60
+ formatter = OutputFormatter(json_mode=state["json_output"])
61
+ config = load_config()
62
+ profile = get_active_profile(config)
63
+ api_url = state.get("api_url") or profile.api_url
64
+ timeout = state.get("timeout") or profile.timeout
65
+
66
+ if not profile.token and profile.auth_type == "jwt":
67
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
68
+ raise typer.Exit(1)
69
+
70
+ async with O2Client(api_url, timeout) as client:
71
+ client.set_jwt(profile.token)
72
+ if profile.api_key_id:
73
+ client.set_api_key(profile.api_key_id, profile.api_secret)
74
+ try:
75
+ data = await client.set_leverage(market_id, leverage_val)
76
+ formatter.print_raw(data)
77
+ if not state["json_output"]:
78
+ formatter.print_success(
79
+ f"Leverage set to {leverage_val}x for market {market_id}"
80
+ )
81
+ except APIError as e:
82
+ formatter.print_error(str(e), e.code)
83
+ raise typer.Exit(1)
84
+ except ConnectionError as e:
85
+ formatter.print_error(str(e))
86
+ raise typer.Exit(1)
87
+
88
+
89
+ @app.command("margin-mode")
90
+ def margin_mode(
91
+ mode: str = typer.Option(
92
+ ..., "--mode", "-m", help="Margin mode (cross/isolated)"
93
+ ),
94
+ ):
95
+ """Set global margin mode."""
96
+ asyncio.run(_margin_mode(mode))
97
+
98
+
99
+ async def _margin_mode(mode: str):
100
+ state = get_state()
101
+ formatter = OutputFormatter(json_mode=state["json_output"])
102
+ config = load_config()
103
+ profile = get_active_profile(config)
104
+ api_url = state.get("api_url") or profile.api_url
105
+ timeout = state.get("timeout") or profile.timeout
106
+
107
+ if not profile.token and profile.auth_type == "jwt":
108
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
109
+ raise typer.Exit(1)
110
+
111
+ if mode not in ("cross", "isolated"):
112
+ formatter.print_error("Margin mode must be 'cross' or 'isolated'.")
113
+ raise typer.Exit(1)
114
+
115
+ async with O2Client(api_url, timeout) as client:
116
+ client.set_jwt(profile.token)
117
+ if profile.api_key_id:
118
+ client.set_api_key(profile.api_key_id, profile.api_secret)
119
+ try:
120
+ data = await client.set_margin_mode(mode)
121
+ formatter.print_raw(data)
122
+ if not state["json_output"]:
123
+ formatter.print_success(f"Margin mode set to {mode}")
124
+ except APIError as e:
125
+ formatter.print_error(str(e), e.code)
126
+ raise typer.Exit(1)
127
+ except ConnectionError as e:
128
+ formatter.print_error(str(e))
129
+ raise typer.Exit(1)
@@ -0,0 +1,78 @@
1
+ """o2 setup commands - Install skill files for vibe coding tools."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ import typer
7
+
8
+ from o2_cli.setup import (
9
+ interactive_setup,
10
+ setup_tool,
11
+ update_skills,
12
+ show_skill,
13
+ show_status,
14
+ ALL_TOOLS,
15
+ )
16
+
17
+ app = typer.Typer(help="Setup and configure O2 CLI for your coding tool")
18
+
19
+
20
+ @app.callback(invoke_without_command=True)
21
+ def setup_main(
22
+ tool: Optional[str] = typer.Option(
23
+ None, "--tool", "-t",
24
+ help=f"Tool name ({', '.join(t.name for t in ALL_TOOLS)}). Interactive if omitted."
25
+ ),
26
+ scope: str = typer.Option(
27
+ "project", "--scope", "-s",
28
+ help="Install scope: global or project"
29
+ ),
30
+ update: bool = typer.Option(
31
+ False, "--update", "-u",
32
+ help="Re-install skill files for all previously configured tools"
33
+ ),
34
+ show: bool = typer.Option(
35
+ False, "--show-skill",
36
+ help="Print the full skill content"
37
+ ),
38
+ status: bool = typer.Option(
39
+ False, "--status",
40
+ help="Show current setup status"
41
+ ),
42
+ ):
43
+ """Setup O2 CLI for your vibe coding tool.
44
+
45
+ \b
46
+ Interactive mode:
47
+ o2 setup
48
+
49
+ \b
50
+ Non-interactive (for agents/CI):
51
+ o2 setup --tool claude-code --scope global
52
+ o2 setup --tool cursor --scope project
53
+
54
+ \b
55
+ Update all configured tools:
56
+ o2 setup --update
57
+
58
+ \b
59
+ Available tools: claude-code, cursor, codex, windsurf, cline, trae
60
+ """
61
+ if show:
62
+ show_skill()
63
+ return
64
+
65
+ if status:
66
+ show_status()
67
+ return
68
+
69
+ if update:
70
+ update_skills()
71
+ return
72
+
73
+ if tool:
74
+ setup_tool(tool, scope)
75
+ return
76
+
77
+ # Default: interactive wizard
78
+ interactive_setup()
@@ -0,0 +1,86 @@
1
+ """o2 trades commands - Trade history."""
2
+
3
+ import asyncio
4
+ from typing import Optional
5
+
6
+ import typer
7
+
8
+ from o2_cli.cli import get_state
9
+ from o2_cli.client import O2Client
10
+ from o2_cli.config import load_config, get_active_profile
11
+ from o2_cli.exceptions import APIError, ConnectionError
12
+ from o2_cli.output import OutputFormatter
13
+
14
+ app = typer.Typer(help="Trade history")
15
+
16
+
17
+ @app.command("list")
18
+ def list_trades(
19
+ market_id: Optional[int] = typer.Option(
20
+ None, "--market-id", "-m", help="Filter by market ID"
21
+ ),
22
+ limit: int = typer.Option(50, "--limit", "-n", help="Number of records"),
23
+ skip: int = typer.Option(0, "--skip", help="Number of records to skip"),
24
+ ):
25
+ """List trade history."""
26
+ asyncio.run(_list_trades(market_id, limit, skip))
27
+
28
+
29
+ async def _list_trades(market_id: Optional[int], limit: int, skip: int):
30
+ state = get_state()
31
+ formatter = OutputFormatter(json_mode=state["json_output"])
32
+ config = load_config()
33
+ profile = get_active_profile(config)
34
+ api_url = state.get("api_url") or profile.api_url
35
+ timeout = state.get("timeout") or profile.timeout
36
+
37
+ if not profile.token and profile.auth_type == "jwt":
38
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
39
+ raise typer.Exit(1)
40
+
41
+ async with O2Client(api_url, timeout) as client:
42
+ client.set_jwt(profile.token)
43
+ if profile.api_key_id:
44
+ client.set_api_key(profile.api_key_id, profile.api_secret)
45
+ try:
46
+ data = await client.get_trades(skip=skip, limit=limit, market_id=market_id)
47
+ formatter.print_raw(data)
48
+ except APIError as e:
49
+ formatter.print_error(str(e), e.code)
50
+ raise typer.Exit(1)
51
+ except ConnectionError as e:
52
+ formatter.print_error(str(e))
53
+ raise typer.Exit(1)
54
+
55
+
56
+ @app.command("summary")
57
+ def summary():
58
+ """Show trade summary statistics."""
59
+ asyncio.run(_summary())
60
+
61
+
62
+ async def _summary():
63
+ state = get_state()
64
+ formatter = OutputFormatter(json_mode=state["json_output"])
65
+ config = load_config()
66
+ profile = get_active_profile(config)
67
+ api_url = state.get("api_url") or profile.api_url
68
+ timeout = state.get("timeout") or profile.timeout
69
+
70
+ if not profile.token and profile.auth_type == "jwt":
71
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
72
+ raise typer.Exit(1)
73
+
74
+ async with O2Client(api_url, timeout) as client:
75
+ client.set_jwt(profile.token)
76
+ if profile.api_key_id:
77
+ client.set_api_key(profile.api_key_id, profile.api_secret)
78
+ try:
79
+ data = await client.get_trade_summary()
80
+ formatter.print_raw(data)
81
+ except APIError as e:
82
+ formatter.print_error(str(e), e.code)
83
+ raise typer.Exit(1)
84
+ except ConnectionError as e:
85
+ formatter.print_error(str(e))
86
+ raise typer.Exit(1)
@@ -0,0 +1,175 @@
1
+ """o2 withdrawals commands - Withdrawal management."""
2
+
3
+ import asyncio
4
+
5
+ import typer
6
+
7
+ from o2_cli.cli import get_state
8
+ from o2_cli.client import O2Client
9
+ from o2_cli.config import load_config, get_active_profile
10
+ from o2_cli.exceptions import APIError, ConnectionError
11
+ from o2_cli.output import OutputFormatter
12
+
13
+ app = typer.Typer(help="Withdrawal management")
14
+
15
+
16
+ @app.command("create")
17
+ def create(
18
+ amount: str = typer.Option(..., "--amount", "-a", help="Withdrawal amount (USDC)"),
19
+ address: str = typer.Option(
20
+ ..., "--address", "-d", help="Destination wallet address"
21
+ ),
22
+ chain: str = typer.Option(
23
+ "ethereum", "--chain", "-c", help="Blockchain network (ethereum/base/arbitrum)"
24
+ ),
25
+ currency: str = typer.Option(
26
+ "USDC", "--currency", help="Currency to withdraw"
27
+ ),
28
+ ):
29
+ """Create a new withdrawal request."""
30
+ asyncio.run(_create(amount, address, chain, currency))
31
+
32
+
33
+ async def _create(amount: str, address: str, chain: str, currency: str):
34
+ state = get_state()
35
+ formatter = OutputFormatter(json_mode=state["json_output"])
36
+ config = load_config()
37
+ profile = get_active_profile(config)
38
+ api_url = state.get("api_url") or profile.api_url
39
+ timeout = state.get("timeout") or profile.timeout
40
+
41
+ if not profile.token and profile.auth_type == "jwt":
42
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
43
+ raise typer.Exit(1)
44
+
45
+ async with O2Client(api_url, timeout) as client:
46
+ client.set_jwt(profile.token)
47
+ if profile.api_key_id:
48
+ client.set_api_key(profile.api_key_id, profile.api_secret)
49
+ try:
50
+ data = await client.create_withdrawal(
51
+ amount=amount, address=address, chain=chain, currency=currency
52
+ )
53
+ formatter.print_raw(data)
54
+ if not state["json_output"]:
55
+ formatter.print_success(
56
+ f"Withdrawal request created: {amount} {currency} to {address[:10]}..."
57
+ )
58
+ except APIError as e:
59
+ formatter.print_error(str(e), e.code)
60
+ raise typer.Exit(1)
61
+ except ConnectionError as e:
62
+ formatter.print_error(str(e))
63
+ raise typer.Exit(1)
64
+
65
+
66
+ @app.command("status")
67
+ def status(
68
+ id: str = typer.Option(
69
+ ..., "--id", "-i", help="Withdrawal ID to check"
70
+ ),
71
+ ):
72
+ """Check withdrawal status."""
73
+ asyncio.run(_status(id))
74
+
75
+
76
+ async def _status(withdrawal_id: str):
77
+ state = get_state()
78
+ formatter = OutputFormatter(json_mode=state["json_output"])
79
+ config = load_config()
80
+ profile = get_active_profile(config)
81
+ api_url = state.get("api_url") or profile.api_url
82
+ timeout = state.get("timeout") or profile.timeout
83
+
84
+ if not profile.token and profile.auth_type == "jwt":
85
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
86
+ raise typer.Exit(1)
87
+
88
+ async with O2Client(api_url, timeout) as client:
89
+ client.set_jwt(profile.token)
90
+ if profile.api_key_id:
91
+ client.set_api_key(profile.api_key_id, profile.api_secret)
92
+ try:
93
+ data = await client.get_withdrawal(withdrawal_id)
94
+ formatter.print_raw(data)
95
+ except APIError as e:
96
+ formatter.print_error(str(e), e.code)
97
+ raise typer.Exit(1)
98
+ except ConnectionError as e:
99
+ formatter.print_error(str(e))
100
+ raise typer.Exit(1)
101
+
102
+
103
+ @app.command("cancel")
104
+ def cancel(
105
+ id: str = typer.Option(
106
+ ..., "--id", "-i", help="Withdrawal ID to cancel"
107
+ ),
108
+ ):
109
+ """Cancel a pending withdrawal."""
110
+ asyncio.run(_cancel(id))
111
+
112
+
113
+ async def _cancel(withdrawal_id: str):
114
+ state = get_state()
115
+ formatter = OutputFormatter(json_mode=state["json_output"])
116
+ config = load_config()
117
+ profile = get_active_profile(config)
118
+ api_url = state.get("api_url") or profile.api_url
119
+ timeout = state.get("timeout") or profile.timeout
120
+
121
+ if not profile.token and profile.auth_type == "jwt":
122
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
123
+ raise typer.Exit(1)
124
+
125
+ async with O2Client(api_url, timeout) as client:
126
+ client.set_jwt(profile.token)
127
+ if profile.api_key_id:
128
+ client.set_api_key(profile.api_key_id, profile.api_secret)
129
+ try:
130
+ data = await client.cancel_withdrawal(withdrawal_id)
131
+ formatter.print_raw(data)
132
+ if not state["json_output"]:
133
+ formatter.print_success(f"Withdrawal {withdrawal_id} cancelled")
134
+ except APIError as e:
135
+ formatter.print_error(str(e), e.code)
136
+ raise typer.Exit(1)
137
+ except ConnectionError as e:
138
+ formatter.print_error(str(e))
139
+ raise typer.Exit(1)
140
+
141
+
142
+ @app.command("list")
143
+ def list_withdrawals(
144
+ limit: int = typer.Option(20, "--limit", "-n", help="Number of records"),
145
+ offset: int = typer.Option(0, "--offset", help="Offset"),
146
+ ):
147
+ """List withdrawal history."""
148
+ asyncio.run(_list_withdrawals(limit, offset))
149
+
150
+
151
+ async def _list_withdrawals(limit: int, offset: int):
152
+ state = get_state()
153
+ formatter = OutputFormatter(json_mode=state["json_output"])
154
+ config = load_config()
155
+ profile = get_active_profile(config)
156
+ api_url = state.get("api_url") or profile.api_url
157
+ timeout = state.get("timeout") or profile.timeout
158
+
159
+ if not profile.token and profile.auth_type == "jwt":
160
+ formatter.print_error("Not authenticated. Run 'o2 auth test-login' first.")
161
+ raise typer.Exit(1)
162
+
163
+ async with O2Client(api_url, timeout) as client:
164
+ client.set_jwt(profile.token)
165
+ if profile.api_key_id:
166
+ client.set_api_key(profile.api_key_id, profile.api_secret)
167
+ try:
168
+ data = await client.get_withdrawal_history(limit, offset)
169
+ formatter.print_raw(data)
170
+ except APIError as e:
171
+ formatter.print_error(str(e), e.code)
172
+ raise typer.Exit(1)
173
+ except ConnectionError as e:
174
+ formatter.print_error(str(e))
175
+ raise typer.Exit(1)