vibe-cli 0.1.0__tar.gz

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,89 @@
1
+ Metadata-Version: 2.2
2
+ Name: vibe-cli
3
+ Version: 0.1.0
4
+ Summary: VIBE Airforce CLI — Web3 trading and data at your fingertips
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: typer>=0.12.0
9
+ Requires-Dist: httpx>=0.27.0
10
+ Requires-Dist: rich>=13.0.0
11
+ Requires-Dist: pydantic>=2.0.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8.0; extra == "dev"
14
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
15
+
16
+ # vibe-cli
17
+
18
+ Web3 trading and data at your fingertips.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ # pip
24
+ pip install vibe-cli
25
+
26
+ # pipx (isolated)
27
+ pipx install vibe-cli
28
+
29
+ # curl | sh
30
+ curl -fsSL https://api.vibe.airforce/api/cli/install.sh | sh
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```bash
36
+ vibe sync # Fetch operations manifest (works without API key)
37
+ vibe list-operations # List all commands
38
+ vibe swap-quote --from-token So11111111111111111111111111111111111111112 \
39
+ --to-token EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v \
40
+ --amount 1000000
41
+ vibe evm-swap-quote --network base --from-asset eth --to-asset usdc --amount 0.1
42
+ ```
43
+
44
+ ## Auth
45
+
46
+ No API key required to try — 30 free credits per day.
47
+
48
+ ```bash
49
+ vibe auth # Paste your API key interactively
50
+ # Or set environment variable
51
+ export VIBE_API_KEY=pk_xxx:sk_xxx
52
+ ```
53
+
54
+ Get your API key at [vibe.airforce/settings/api-keys](https://www.vibe.airforce/settings/api-keys)
55
+
56
+ ## Commands
57
+
58
+ | Command | Description |
59
+ |---------|-------------|
60
+ | `vibe sync` | Fetch operations manifest |
61
+ | `vibe list-operations` | List available commands |
62
+ | `vibe auth` | Authenticate with API key |
63
+ | `vibe logout` | Remove saved credentials |
64
+ | `vibe swap-quote` | Solana swap quote |
65
+ | `vibe swap` | Execute Solana swap |
66
+ | `vibe evm-swap-quote` | Base/Ethereum swap quote |
67
+ | `vibe evm-swap` | Execute Base/Ethereum swap |
68
+ | `vibe defi-discover` | Discover DeFi protocols |
69
+ | `vibe defi-quote` | Get DeFi position quote |
70
+ | `vibe defi-deposit` | Execute DeFi deposit |
71
+ | `vibe bags-launch-token` | Launch token on bags.fm |
72
+ | `vibe bags-claim-fees` | Claim bags.fm fees |
73
+ | `vibe bags-positions` | View bags.fm positions |
74
+ | `vibe data-provider` | Query external data providers |
75
+ | `vibe feedback` | Submit feedback |
76
+ | `vibe version` | Show CLI version |
77
+
78
+ ## Output Formats
79
+
80
+ ```bash
81
+ vibe swap-quote -o table ... # Human-readable (default)
82
+ vibe swap-quote -o json ... # Structured JSON
83
+ vibe swap-quote -o json -f body.data ... # Extract nested field
84
+ vibe swap-quote -o raw ... # Unformatted JSON
85
+ ```
86
+
87
+ ## License
88
+
89
+ MIT
@@ -0,0 +1,74 @@
1
+ # vibe-cli
2
+
3
+ Web3 trading and data at your fingertips.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ # pip
9
+ pip install vibe-cli
10
+
11
+ # pipx (isolated)
12
+ pipx install vibe-cli
13
+
14
+ # curl | sh
15
+ curl -fsSL https://api.vibe.airforce/api/cli/install.sh | sh
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```bash
21
+ vibe sync # Fetch operations manifest (works without API key)
22
+ vibe list-operations # List all commands
23
+ vibe swap-quote --from-token So11111111111111111111111111111111111111112 \
24
+ --to-token EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v \
25
+ --amount 1000000
26
+ vibe evm-swap-quote --network base --from-asset eth --to-asset usdc --amount 0.1
27
+ ```
28
+
29
+ ## Auth
30
+
31
+ No API key required to try — 30 free credits per day.
32
+
33
+ ```bash
34
+ vibe auth # Paste your API key interactively
35
+ # Or set environment variable
36
+ export VIBE_API_KEY=pk_xxx:sk_xxx
37
+ ```
38
+
39
+ Get your API key at [vibe.airforce/settings/api-keys](https://www.vibe.airforce/settings/api-keys)
40
+
41
+ ## Commands
42
+
43
+ | Command | Description |
44
+ |---------|-------------|
45
+ | `vibe sync` | Fetch operations manifest |
46
+ | `vibe list-operations` | List available commands |
47
+ | `vibe auth` | Authenticate with API key |
48
+ | `vibe logout` | Remove saved credentials |
49
+ | `vibe swap-quote` | Solana swap quote |
50
+ | `vibe swap` | Execute Solana swap |
51
+ | `vibe evm-swap-quote` | Base/Ethereum swap quote |
52
+ | `vibe evm-swap` | Execute Base/Ethereum swap |
53
+ | `vibe defi-discover` | Discover DeFi protocols |
54
+ | `vibe defi-quote` | Get DeFi position quote |
55
+ | `vibe defi-deposit` | Execute DeFi deposit |
56
+ | `vibe bags-launch-token` | Launch token on bags.fm |
57
+ | `vibe bags-claim-fees` | Claim bags.fm fees |
58
+ | `vibe bags-positions` | View bags.fm positions |
59
+ | `vibe data-provider` | Query external data providers |
60
+ | `vibe feedback` | Submit feedback |
61
+ | `vibe version` | Show CLI version |
62
+
63
+ ## Output Formats
64
+
65
+ ```bash
66
+ vibe swap-quote -o table ... # Human-readable (default)
67
+ vibe swap-quote -o json ... # Structured JSON
68
+ vibe swap-quote -o json -f body.data ... # Extract nested field
69
+ vibe swap-quote -o raw ... # Unformatted JSON
70
+ ```
71
+
72
+ ## License
73
+
74
+ MIT
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0,<77.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "vibe-cli"
7
+ version = "0.1.0"
8
+ description = "VIBE Airforce CLI — Web3 trading and data at your fingertips"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = {text = "MIT"}
12
+ dependencies = [
13
+ "typer>=0.12.0",
14
+ "httpx>=0.27.0",
15
+ "rich>=13.0.0",
16
+ "pydantic>=2.0.0",
17
+ ]
18
+
19
+ [project.scripts]
20
+ vibe = "vibe_cli.main:app"
21
+
22
+ [project.optional-dependencies]
23
+ dev = [
24
+ "pytest>=8.0",
25
+ "ruff>=0.4.0",
26
+ ]
27
+
28
+ [tool.setuptools]
29
+ license-files = []
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,3 @@
1
+ """VIBE Airforce CLI — Web3 trading and data at your fingertips."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,108 @@
1
+ """Authentication flow for the VIBE CLI."""
2
+
3
+ from typing import Optional
4
+
5
+ from rich.console import Console
6
+
7
+ from vibe_cli.config import load_config, save_config, get_api_key, get_trial_token, save_trial_token
8
+
9
+ console = Console()
10
+
11
+
12
+ def login(interactive: bool = True) -> Optional[str]:
13
+ """
14
+ Authenticate with the VIBE API.
15
+
16
+ If interactive, prompt the user to paste their API key.
17
+ Otherwise, check config and env vars.
18
+ """
19
+ # Check existing key
20
+ existing = get_api_key()
21
+ if existing and not interactive:
22
+ return existing
23
+
24
+ if interactive:
25
+ console.print("\n[bold]VIBE Airforce Authentication[/bold]")
26
+ console.print("Get your API key at: [link]https://www.vibe.airforce/settings/api-keys[/link]")
27
+ console.print()
28
+ console.print("Your API key looks like: [dim]pk_xxxxxxxx:sk_xxxxxxxxxxxxxxxx[/dim]")
29
+ console.print()
30
+
31
+ api_key = console.input("[bold]Paste your API key:[/bold] ").strip()
32
+
33
+ if not api_key or ":" not in api_key:
34
+ console.print("[red]Invalid API key format. Expected: pk_xxx:sk_xxx[/red]")
35
+ return None
36
+
37
+ # Save to config
38
+ config = load_config()
39
+ config["api_key"] = api_key
40
+ save_config(config)
41
+
42
+ console.print("[green]API key saved![/green] You can now use all VIBE commands.")
43
+ return api_key
44
+
45
+ return None
46
+
47
+
48
+ def logout() -> None:
49
+ """Remove saved API key and trial token."""
50
+ config = load_config()
51
+ removed = False
52
+ if "api_key" in config:
53
+ del config["api_key"]
54
+ removed = True
55
+ if "trial_token" in config:
56
+ del config["trial_token"]
57
+ removed = True
58
+ save_config(config)
59
+ if removed:
60
+ console.print("[green]Logged out successfully.[/green]")
61
+ else:
62
+ console.print("[dim]No API key found in config.[/dim]")
63
+
64
+
65
+ def _ensure_trial_token() -> Optional[str]:
66
+ """Get a trial token if no API key is configured. Cache it in config."""
67
+ # Don't request trial if user has an API key
68
+ if get_api_key():
69
+ return None
70
+
71
+ # Check for cached trial token
72
+ cached = get_trial_token()
73
+ if cached:
74
+ return cached
75
+
76
+ # Request a new trial token
77
+ try:
78
+ import httpx
79
+ from vibe_cli.config import get_base_url
80
+ base_url = get_base_url().rstrip("/")
81
+ resp = httpx.post(f"{base_url}/api/vibe-tools/trial-auth", timeout=10.0)
82
+ if resp.status_code == 200:
83
+ data = resp.json().get("data", {})
84
+ token = data.get("token")
85
+ if token:
86
+ save_trial_token(token)
87
+ return token
88
+ except Exception:
89
+ pass
90
+
91
+ return None
92
+
93
+
94
+ def get_auth_header() -> dict:
95
+ """Get Authorization header for API requests.
96
+
97
+ Uses API key if configured, otherwise auto-obtains a trial token.
98
+ """
99
+ api_key = get_api_key()
100
+ if api_key:
101
+ return {"Authorization": f"Bearer {api_key}"}
102
+
103
+ # Auto-trial: get a free trial token so unauthenticated users can explore
104
+ trial = _ensure_trial_token()
105
+ if trial:
106
+ return {"Authorization": f"Bearer {trial}"}
107
+
108
+ return {}
@@ -0,0 +1,133 @@
1
+ """HTTP client for the VIBE Tools Gateway."""
2
+
3
+ from typing import Any, Optional
4
+
5
+ import httpx
6
+ from rich.console import Console
7
+
8
+ from vibe_cli.config import get_base_url
9
+ from vibe_cli.auth import get_auth_header
10
+
11
+ console = Console()
12
+
13
+
14
+ class VibeClient:
15
+ """Thin HTTP client for /vibe-tools/* routes."""
16
+
17
+ def __init__(self, base_url: Optional[str] = None):
18
+ self.base_url = (base_url or get_base_url()).rstrip("/")
19
+
20
+ async def request(
21
+ self,
22
+ method: str,
23
+ path: str,
24
+ json_body: Optional[dict] = None,
25
+ params: Optional[dict] = None,
26
+ ) -> dict:
27
+ """Make an authenticated request to the vibe-tools gateway."""
28
+ url = f"{self.base_url}/api{path}"
29
+ headers = get_auth_header()
30
+ headers["Content-Type"] = "application/json"
31
+
32
+ async with httpx.AsyncClient(timeout=30.0) as client:
33
+ response = await client.request(
34
+ method=method,
35
+ url=url,
36
+ headers=headers,
37
+ json=json_body,
38
+ params=params,
39
+ )
40
+
41
+ return self._handle_response(response)
42
+
43
+ def request_sync(
44
+ self,
45
+ method: str,
46
+ path: str,
47
+ json_body: Optional[dict] = None,
48
+ params: Optional[dict] = None,
49
+ ) -> dict:
50
+ """Synchronous version of request."""
51
+ url = f"{self.base_url}/api{path}"
52
+ headers = get_auth_header()
53
+ headers["Content-Type"] = "application/json"
54
+
55
+ with httpx.Client(timeout=30.0) as client:
56
+ response = client.request(
57
+ method=method,
58
+ url=url,
59
+ headers=headers,
60
+ json=json_body,
61
+ params=params,
62
+ )
63
+
64
+ return self._handle_response(response)
65
+
66
+ @staticmethod
67
+ def _handle_response(response: httpx.Response) -> dict:
68
+ """Parse response and handle errors."""
69
+ try:
70
+ body = response.json()
71
+ except Exception:
72
+ return {"error": {"code": "PARSE_ERROR", "message": response.text}}
73
+
74
+ if response.status_code >= 400:
75
+ error = body.get("error", {})
76
+ error.setdefault("code", "UNKNOWN")
77
+ error.setdefault("message", f"HTTP {response.status_code}")
78
+ return {"error": error}
79
+
80
+ return body
81
+
82
+ # Convenience methods
83
+
84
+ def get_operations(self) -> dict:
85
+ return self.request_sync("GET", "/vibe-tools/operations")
86
+
87
+ def trial_auth(self) -> dict:
88
+ return self.request_sync("POST", "/vibe-tools/trial-auth")
89
+
90
+ def data_provider(self, service_name: str, route: str, payload: Optional[dict] = None) -> dict:
91
+ return self.request_sync("POST", "/vibe-tools/data-provider", {
92
+ "service_name": service_name,
93
+ "route": route,
94
+ "payload": payload,
95
+ })
96
+
97
+ def swap_quote(self, input_mint: str, output_mint: str, amount: int, **kwargs) -> dict:
98
+ body = {"input_mint": input_mint, "output_mint": output_mint, "amount": amount, **kwargs}
99
+ return self.request_sync("POST", "/vibe-tools/swap-quote", body)
100
+
101
+ def swap(self, input_mint: str, output_mint: str, amount: int, **kwargs) -> dict:
102
+ body = {"input_mint": input_mint, "output_mint": output_mint, "amount": amount, **kwargs}
103
+ return self.request_sync("POST", "/vibe-tools/swap", body)
104
+
105
+ def evm_swap_quote(self, network: str, from_asset: str, to_asset: str, amount: str, **kwargs) -> dict:
106
+ body = {"network": network, "from_asset": from_asset, "to_asset": to_asset, "amount": amount, **kwargs}
107
+ return self.request_sync("POST", "/vibe-tools/evm/swap-quote", body)
108
+
109
+ def evm_swap(self, network: str, from_asset: str, to_asset: str, amount: str, **kwargs) -> dict:
110
+ body = {"network": network, "from_asset": from_asset, "to_asset": to_asset, "amount": amount, **kwargs}
111
+ return self.request_sync("POST", "/vibe-tools/evm/swap", body)
112
+
113
+ def defi_discover(self, protocols: Optional[list] = None) -> dict:
114
+ body = {"protocols": protocols} if protocols else {}
115
+ return self.request_sync("POST", "/vibe-tools/defi/discover", body)
116
+
117
+ def defi_quote(self, action: str, token: str, amount: str, protocol: str = "marinade", **kwargs) -> dict:
118
+ body = {"action": action, "token": token, "amount": amount, "protocol": protocol, **kwargs}
119
+ return self.request_sync("POST", "/vibe-tools/defi/quote", body)
120
+
121
+ def defi_deposit(self, action: str, token: str, amount: str, protocol: str = "marinade", **kwargs) -> dict:
122
+ body = {"action": action, "token": token, "amount": amount, "protocol": protocol, **kwargs}
123
+ return self.request_sync("POST", "/vibe-tools/defi/deposit", body)
124
+
125
+ def bags_launch_token(self, name: str, symbol: str, description: str, image_url: str, **kwargs) -> dict:
126
+ body = {"name": name, "symbol": symbol, "description": description, "image_url": image_url, **kwargs}
127
+ return self.request_sync("POST", "/vibe-tools/bags/launch-token", body)
128
+
129
+ def bags_claim_fees(self, token_mint: str) -> dict:
130
+ return self.request_sync("POST", "/vibe-tools/bags/claim-fees", {"token_mint": token_mint})
131
+
132
+ def bags_positions(self) -> dict:
133
+ return self.request_sync("GET", "/vibe-tools/bags/positions")
@@ -0,0 +1,77 @@
1
+ """Configuration management for the VIBE CLI."""
2
+
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+
9
+ CONFIG_DIR = Path.home() / ".config" / "vibe"
10
+ CONFIG_FILE = CONFIG_DIR / "config.json"
11
+ MANIFEST_CACHE_FILE = CONFIG_DIR / "manifest.json"
12
+
13
+
14
+ def _ensure_config_dir() -> None:
15
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
16
+
17
+
18
+ def load_config() -> dict:
19
+ """Load CLI config from disk."""
20
+ if CONFIG_FILE.exists():
21
+ try:
22
+ return json.loads(CONFIG_FILE.read_text())
23
+ except (json.JSONDecodeError, OSError):
24
+ return {}
25
+ return {}
26
+
27
+
28
+ def save_config(config: dict) -> None:
29
+ """Save CLI config to disk."""
30
+ _ensure_config_dir()
31
+ CONFIG_FILE.write_text(json.dumps(config, indent=2))
32
+
33
+
34
+ def get_api_key() -> Optional[str]:
35
+ """Get API key from env var, config, or return None."""
36
+ # 1. Environment variable
37
+ env_key = os.environ.get("VIBE_API_KEY")
38
+ if env_key:
39
+ return env_key
40
+
41
+ # 2. Config file
42
+ config = load_config()
43
+ return config.get("api_key")
44
+
45
+
46
+ def get_trial_token() -> Optional[str]:
47
+ """Get cached trial token from config."""
48
+ config = load_config()
49
+ return config.get("trial_token")
50
+
51
+
52
+ def save_trial_token(token: str) -> None:
53
+ """Save trial token to config."""
54
+ config = load_config()
55
+ config["trial_token"] = token
56
+ save_config(config)
57
+
58
+
59
+ def get_base_url() -> str:
60
+ """Get the VIBE API base URL."""
61
+ return os.environ.get("VIBE_API_URL", "https://api.vibe.airforce")
62
+
63
+
64
+ def get_manifest_cache() -> Optional[dict]:
65
+ """Load cached operations manifest."""
66
+ if MANIFEST_CACHE_FILE.exists():
67
+ try:
68
+ return json.loads(MANIFEST_CACHE_FILE.read_text())
69
+ except (json.JSONDecodeError, OSError):
70
+ return None
71
+ return None
72
+
73
+
74
+ def save_manifest_cache(manifest: dict) -> None:
75
+ """Cache the operations manifest."""
76
+ _ensure_config_dir()
77
+ MANIFEST_CACHE_FILE.write_text(json.dumps(manifest, indent=2))
@@ -0,0 +1,283 @@
1
+ """VIBE Airforce CLI — Web3 trading and data at your fingertips.
2
+
3
+ Usage:
4
+ vibe sync Fetch operations manifest
5
+ vibe list-operations List available commands
6
+ vibe auth Authenticate with API key
7
+ vibe <command> [options] Execute a vibe-tools command
8
+
9
+ Examples:
10
+ vibe sync
11
+ vibe list-operations | grep swap
12
+ vibe swap-quote --from-token So11111111111111111111111111111111111111112 \
13
+ --to-token EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v \
14
+ --amount 1000000
15
+ vibe evm-swap-quote --network base --from-asset eth --to-asset usdc --amount 0.1
16
+ """
17
+
18
+ import json as _json
19
+ from typing import Optional
20
+
21
+ import typer
22
+ from rich.console import Console
23
+
24
+ from vibe_cli import __version__
25
+ from vibe_cli.auth import login, logout as auth_logout
26
+ from vibe_cli.config import get_api_key
27
+ from vibe_cli.operations import sync_manifest, list_operations, execute_operation
28
+ from vibe_cli.output import format_output, print_error
29
+
30
+ console = Console()
31
+
32
+ app = typer.Typer(
33
+ name="vibe",
34
+ help="VIBE Airforce — Web3 trading and data at your fingertips",
35
+ no_args_is_help=True,
36
+ add_completion=False,
37
+ )
38
+
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # Core commands
42
+ # ---------------------------------------------------------------------------
43
+
44
+ @app.command()
45
+ def sync():
46
+ """Fetch the latest operations manifest from the VIBE gateway."""
47
+ console.print("[bold]Syncing operations manifest...[/bold]")
48
+ manifest = sync_manifest()
49
+ console.print(f"[green]Synced {len(manifest)} operations.[/green]")
50
+ console.print("Run [bold]vibe list-operations[/bold] to see available commands.")
51
+
52
+
53
+ @app.command(name="list-operations")
54
+ def list_ops(
55
+ filter: Optional[str] = typer.Argument(None, help="Filter operations by keyword"),
56
+ ):
57
+ """List all available vibe-tools operations."""
58
+ list_operations(filter)
59
+
60
+
61
+ @app.command()
62
+ def auth():
63
+ """Authenticate with your VIBE API key."""
64
+ login(interactive=True)
65
+
66
+
67
+ @app.command()
68
+ def logout():
69
+ """Remove saved API key."""
70
+ auth_logout()
71
+
72
+
73
+ @app.command()
74
+ def install():
75
+ """Update the VIBE CLI to the latest version."""
76
+ console.print("[bold]Checking for updates...[/bold]")
77
+ import subprocess
78
+ subprocess.run(["pip", "install", "--upgrade", "vibe-cli"])
79
+
80
+
81
+ @app.command()
82
+ def feedback(
83
+ message: str = typer.Argument(..., help="Your feedback message"),
84
+ ):
85
+ """Submit feedback about the VIBE CLI."""
86
+ console.print(f"[green]Thank you for your feedback![/green]")
87
+ console.print(f"[dim]Message: {message}[/dim]")
88
+
89
+
90
+ @app.command()
91
+ def version():
92
+ """Show the VIBE CLI version."""
93
+ console.print(f"vibe CLI v{__version__}")
94
+
95
+
96
+ # ---------------------------------------------------------------------------
97
+ # Route commands — explicit functions for each vibe-tools operation
98
+ # ---------------------------------------------------------------------------
99
+
100
+ def _output_opts():
101
+ """Common output options shared by all route commands."""
102
+ return {
103
+ "output": typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
104
+ "field": typer.Option(None, "-f", "--field", help="Extract nested field (dot-path e.g. 'body.data')"),
105
+ }
106
+
107
+
108
+ # --- Data Provider ---
109
+
110
+ @app.command(name="data-provider")
111
+ def data_provider(
112
+ service: str = typer.Option(..., "--service", help="Provider name (e.g. 'nansen', 'twitter')"),
113
+ route: str = typer.Option(..., "--route", help="Endpoint key (e.g. 'smart_money_netflows')"),
114
+ payload: Optional[str] = typer.Option(None, "--payload", help="JSON payload for the provider"),
115
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
116
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
117
+ ):
118
+ """Query external data providers (Nansen, Twitter, etc.)."""
119
+ params: dict = {"service_name": service, "route": route}
120
+ if payload:
121
+ try:
122
+ params["payload"] = _json.loads(payload)
123
+ except _json.JSONDecodeError:
124
+ console.print("[red]Invalid JSON payload[/red]")
125
+ raise typer.Exit(1)
126
+ execute_operation("POST /vibe-tools/data-provider", params, output, field)
127
+
128
+
129
+ # --- Solana Swap ---
130
+
131
+ @app.command(name="swap-quote")
132
+ def swap_quote(
133
+ from_token: str = typer.Option(..., "--from-token", help="Input token mint address"),
134
+ to_token: str = typer.Option(..., "--to-token", help="Output token mint address"),
135
+ amount: int = typer.Option(..., "--amount", help="Amount in lamports"),
136
+ slippage_bps: Optional[int] = typer.Option(None, "--slippage-bps", help="Slippage in basis points"),
137
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
138
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
139
+ ):
140
+ """Get Solana swap quote (Jupiter, Meteora)."""
141
+ params: dict = {"input_mint": from_token, "output_mint": to_token, "amount": amount}
142
+ if slippage_bps is not None:
143
+ params["slippage_bps"] = slippage_bps
144
+ execute_operation("POST /vibe-tools/swap-quote", params, output, field)
145
+
146
+
147
+ @app.command(name="swap")
148
+ def swap(
149
+ from_token: str = typer.Option(..., "--from-token", help="Input token mint address"),
150
+ to_token: str = typer.Option(..., "--to-token", help="Output token mint address"),
151
+ amount: int = typer.Option(..., "--amount", help="Amount in lamports"),
152
+ slippage_bps: Optional[int] = typer.Option(None, "--slippage-bps", help="Slippage in basis points"),
153
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
154
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
155
+ ):
156
+ """Execute Solana swap (returns unsigned transaction)."""
157
+ params: dict = {"input_mint": from_token, "output_mint": to_token, "amount": amount}
158
+ if slippage_bps is not None:
159
+ params["slippage_bps"] = slippage_bps
160
+ execute_operation("POST /vibe-tools/swap", params, output, field)
161
+
162
+
163
+ # --- EVM Swap ---
164
+
165
+ @app.command(name="evm-swap-quote")
166
+ def evm_swap_quote(
167
+ network: str = typer.Option("base", "--network", help="EVM network: 'base' or 'ethereum'"),
168
+ from_asset: str = typer.Option(..., "--from-asset", help="Source asset (e.g. 'eth', 'usdc')"),
169
+ to_asset: str = typer.Option(..., "--to-asset", help="Destination asset"),
170
+ amount: str = typer.Option(..., "--amount", help="Amount in human-readable units (e.g. '0.1')"),
171
+ slippage_pct: Optional[float] = typer.Option(None, "--slippage-pct", help="Max slippage percentage"),
172
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
173
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
174
+ ):
175
+ """Get Base/Ethereum swap quote (0x aggregator)."""
176
+ params: dict = {"network": network, "from_asset": from_asset, "to_asset": to_asset, "amount": amount}
177
+ if slippage_pct is not None:
178
+ params["slippage_pct"] = slippage_pct
179
+ execute_operation("POST /vibe-tools/evm/swap-quote", params, output, field)
180
+
181
+
182
+ @app.command(name="evm-swap")
183
+ def evm_swap(
184
+ network: str = typer.Option("base", "--network", help="EVM network: 'base' or 'ethereum'"),
185
+ from_asset: str = typer.Option(..., "--from-asset", help="Source asset"),
186
+ to_asset: str = typer.Option(..., "--to-asset", help="Destination asset"),
187
+ amount: str = typer.Option(..., "--amount", help="Amount in human-readable units"),
188
+ slippage_pct: Optional[float] = typer.Option(None, "--slippage-pct", help="Max slippage percentage"),
189
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
190
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
191
+ ):
192
+ """Execute Base/Ethereum swap (CDP managed wallet)."""
193
+ params: dict = {"network": network, "from_asset": from_asset, "to_asset": to_asset, "amount": amount}
194
+ if slippage_pct is not None:
195
+ params["slippage_pct"] = slippage_pct
196
+ execute_operation("POST /vibe-tools/evm/swap", params, output, field)
197
+
198
+
199
+ # --- DeFi ---
200
+
201
+ @app.command(name="defi-discover")
202
+ def defi_discover(
203
+ protocols: Optional[str] = typer.Option(None, "--protocols", help="Comma-separated protocol filter"),
204
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
205
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
206
+ ):
207
+ """Discover available DeFi protocols and positions."""
208
+ params: dict = {}
209
+ if protocols:
210
+ params["protocols"] = [p.strip() for p in protocols.split(",")]
211
+ execute_operation("POST /vibe-tools/defi/discover", params, output, field)
212
+
213
+
214
+ @app.command(name="defi-quote")
215
+ def defi_quote(
216
+ action: str = typer.Option(..., "--action", help="DeFi action: stake, supply, borrow, add_liquidity"),
217
+ token: str = typer.Option(..., "--token", help="Token symbol (e.g. 'SOL', 'USDC')"),
218
+ amount: str = typer.Option(..., "--amount", help="Amount as decimal string"),
219
+ protocol: Optional[str] = typer.Option(None, "--protocol", help="Protocol name"),
220
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
221
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
222
+ ):
223
+ """Get DeFi position quote (stake, lend, LP)."""
224
+ params: dict = {"action": action, "token": token, "amount": amount}
225
+ if protocol:
226
+ params["protocol"] = protocol
227
+ execute_operation("POST /vibe-tools/defi/quote", params, output, field)
228
+
229
+
230
+ @app.command(name="defi-deposit")
231
+ def defi_deposit(
232
+ action: str = typer.Option(..., "--action", help="DeFi action: stake, supply, borrow, add_liquidity"),
233
+ token: str = typer.Option(..., "--token", help="Token symbol"),
234
+ amount: str = typer.Option(..., "--amount", help="Amount as decimal string"),
235
+ protocol: Optional[str] = typer.Option(None, "--protocol", help="Protocol name"),
236
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
237
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
238
+ ):
239
+ """Execute DeFi deposit (returns unsigned transaction)."""
240
+ params: dict = {"action": action, "token": token, "amount": amount}
241
+ if protocol:
242
+ params["protocol"] = protocol
243
+ execute_operation("POST /vibe-tools/defi/deposit", params, output, field)
244
+
245
+
246
+ # --- bags.fm ---
247
+
248
+ @app.command(name="bags-launch-token")
249
+ def bags_launch_token(
250
+ name: str = typer.Option(..., "--name", help="Token name"),
251
+ symbol: str = typer.Option(..., "--symbol", help="Token symbol"),
252
+ description: str = typer.Option(..., "--description", help="Token description"),
253
+ image_url: str = typer.Option(..., "--image-url", help="Token image URL (https)"),
254
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
255
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
256
+ ):
257
+ """Launch a token on bags.fm."""
258
+ params: dict = {"name": name, "symbol": symbol, "description": description, "image_url": image_url}
259
+ execute_operation("POST /vibe-tools/bags/launch-token", params, output, field)
260
+
261
+
262
+ @app.command(name="bags-claim-fees")
263
+ def bags_claim_fees(
264
+ token_mint: str = typer.Option(..., "--token-mint", help="Token mint address"),
265
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
266
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
267
+ ):
268
+ """Claim bags.fm trading fees."""
269
+ params: dict = {"token_mint": token_mint}
270
+ execute_operation("POST /vibe-tools/bags/claim-fees", params, output, field)
271
+
272
+
273
+ @app.command(name="bags-positions")
274
+ def bags_positions(
275
+ output: str = typer.Option("table", "-o", "--output", help="Output format: table, json, raw"),
276
+ field: Optional[str] = typer.Option(None, "-f", "--field", help="Extract nested field"),
277
+ ):
278
+ """View bags.fm positions and fees."""
279
+ execute_operation("GET /vibe-tools/bags/positions", {}, output, field)
280
+
281
+
282
+ if __name__ == "__main__":
283
+ app()
@@ -0,0 +1,181 @@
1
+ """Dynamic command generation from the /vibe-tools/operations manifest."""
2
+
3
+ from typing import Any, Callable, Optional
4
+
5
+ import typer
6
+ from rich.console import Console
7
+
8
+ from vibe_cli.client import VibeClient
9
+ from vibe_cli.config import get_manifest_cache, save_manifest_cache
10
+ from vibe_cli.output import format_output, print_error
11
+
12
+ console = Console()
13
+
14
+
15
+ # Mapping from manifest path to client method name
16
+ _ROUTE_TO_METHOD = {
17
+ "POST /vibe-tools/data-provider": "data_provider",
18
+ "POST /vibe-tools/swap-quote": "swap_quote",
19
+ "POST /vibe-tools/swap": "swap",
20
+ "POST /vibe-tools/evm/swap-quote": "evm_swap_quote",
21
+ "POST /vibe-tools/evm/swap": "evm_swap",
22
+ "POST /vibe-tools/defi/discover": "defi_discover",
23
+ "POST /vibe-tools/defi/quote": "defi_quote",
24
+ "POST /vibe-tools/defi/deposit": "defi_deposit",
25
+ "POST /vibe-tools/bags/launch-token": "bags_launch_token",
26
+ "POST /vibe-tools/bags/claim-fees": "bags_claim_fees",
27
+ "GET /vibe-tools/bags/positions": "bags_positions",
28
+ }
29
+
30
+ # Parameter definitions for each route (maps CLI flag → request body field)
31
+ _ROUTE_PARAMS = {
32
+ "POST /vibe-tools/data-provider": [
33
+ ("--service", "service_name", str, "Provider name (e.g. 'nansen', 'twitter')"),
34
+ ("--route", "route", str, "Endpoint key (e.g. 'smart_money_netflows')"),
35
+ ("--payload", "payload", Optional[str], "JSON payload for the provider"),
36
+ ],
37
+ "POST /vibe-tools/swap-quote": [
38
+ ("--from-token", "input_mint", str, "Input token mint address"),
39
+ ("--to-token", "output_mint", str, "Output token mint address"),
40
+ ("--amount", "amount", int, "Amount in lamports"),
41
+ ("--slippage-bps", "slippage_bps", Optional[int], "Slippage in basis points (default 50)"),
42
+ ],
43
+ "POST /vibe-tools/swap": [
44
+ ("--from-token", "input_mint", str, "Input token mint address"),
45
+ ("--to-token", "output_mint", str, "Output token mint address"),
46
+ ("--amount", "amount", int, "Amount in lamports"),
47
+ ("--slippage-bps", "slippage_bps", Optional[int], "Slippage in basis points"),
48
+ ],
49
+ "POST /vibe-tools/evm/swap-quote": [
50
+ ("--network", "network", str, "EVM network: 'base' or 'ethereum'"),
51
+ ("--from-asset", "from_asset", str, "Source asset (e.g. 'eth', 'usdc')"),
52
+ ("--to-asset", "to_asset", str, "Destination asset"),
53
+ ("--amount", "amount", str, "Amount in human-readable units"),
54
+ ("--slippage-pct", "slippage_pct", Optional[float], "Max slippage percentage"),
55
+ ],
56
+ "POST /vibe-tools/evm/swap": [
57
+ ("--network", "network", str, "EVM network: 'base' or 'ethereum'"),
58
+ ("--from-asset", "from_asset", str, "Source asset"),
59
+ ("--to-asset", "to_asset", str, "Destination asset"),
60
+ ("--amount", "amount", str, "Amount in human-readable units"),
61
+ ("--slippage-pct", "slippage_pct", Optional[float], "Max slippage percentage"),
62
+ ],
63
+ "POST /vibe-tools/defi/discover": [
64
+ ("--protocols", "protocols", Optional[str], "Comma-separated protocol filter"),
65
+ ],
66
+ "POST /vibe-tools/defi/quote": [
67
+ ("--action", "action", str, "DeFi action: stake, supply, borrow, add_liquidity"),
68
+ ("--token", "token", str, "Token symbol"),
69
+ ("--amount", "amount", str, "Amount as decimal string"),
70
+ ("--protocol", "protocol", Optional[str], "Protocol name"),
71
+ ],
72
+ "POST /vibe-tools/defi/deposit": [
73
+ ("--action", "action", str, "DeFi action: stake, supply, borrow, add_liquidity"),
74
+ ("--token", "token", str, "Token symbol"),
75
+ ("--amount", "amount", str, "Amount as decimal string"),
76
+ ("--protocol", "protocol", Optional[str], "Protocol name"),
77
+ ],
78
+ "POST /vibe-tools/bags/launch-token": [
79
+ ("--name", "name", str, "Token name"),
80
+ ("--symbol", "symbol", str, "Token symbol"),
81
+ ("--description", "description", str, "Token description"),
82
+ ("--image-url", "image_url", str, "Token image URL"),
83
+ ],
84
+ "POST /vibe-tools/bags/claim-fees": [
85
+ ("--token-mint", "token_mint", str, "Token mint address"),
86
+ ],
87
+ "GET /vibe-tools/bags/positions": [],
88
+ }
89
+
90
+
91
+ def sync_manifest() -> list[dict]:
92
+ """Fetch the operations manifest from the gateway."""
93
+ client = VibeClient()
94
+ response = client.get_operations()
95
+
96
+ if "error" in response:
97
+ print_error(response["error"])
98
+
99
+ manifest = response.get("data", [])
100
+ save_manifest_cache(manifest)
101
+ return manifest
102
+
103
+
104
+ def get_cached_manifest() -> list[dict]:
105
+ """Get the manifest from cache, or sync if missing."""
106
+ cached = get_manifest_cache()
107
+ if cached:
108
+ return cached
109
+ return sync_manifest()
110
+
111
+
112
+ def list_operations(filter_text: Optional[str] = None) -> None:
113
+ """List all available operations, optionally filtered."""
114
+ manifest = get_cached_manifest()
115
+
116
+ if filter_text:
117
+ manifest = [
118
+ op for op in manifest
119
+ if filter_text.lower() in op.get("path", "").lower()
120
+ or filter_text.lower() in op.get("summary", "").lower()
121
+ ]
122
+
123
+ for op in manifest:
124
+ method = op.get("method", "?")
125
+ path = op.get("path", "?")
126
+ summary = op.get("summary", "")
127
+ scope = op.get("scope", "")
128
+ rate = op.get("rate_limit", "-")
129
+
130
+ console.print(f" [bold cyan]{method:6s}[/bold cyan] {path}")
131
+ console.print(f" [dim]{summary}[/dim]")
132
+ console.print(f" scope={scope} rate={rate}")
133
+ console.print()
134
+
135
+
136
+ def build_route_key(method: str, path: str) -> str:
137
+ """Build a route key like 'POST /vibe-tools/swap-quote'."""
138
+ return f"{method} {path}"
139
+
140
+
141
+ def execute_operation(
142
+ route_key: str,
143
+ params: dict,
144
+ output_format: str = "table",
145
+ filter_path: Optional[str] = None,
146
+ ) -> None:
147
+ """Execute a vibe-tools operation by route key."""
148
+ client = VibeClient()
149
+ method_name = _ROUTE_TO_METHOD.get(route_key)
150
+
151
+ if not method_name:
152
+ console.print(f"[red]Unknown operation: {route_key}[/red]")
153
+ raise typer.Exit(1)
154
+
155
+ method_func = getattr(client, method_name, None)
156
+ if not method_func:
157
+ console.print(f"[red]Client method not found: {method_name}[/red]")
158
+ raise typer.Exit(1)
159
+
160
+ # Clean None values from params
161
+ clean_params = {k: v for k, v in params.items() if v is not None}
162
+
163
+ # Parse JSON payloads
164
+ if "payload" in clean_params and isinstance(clean_params["payload"], str):
165
+ import json
166
+ try:
167
+ clean_params["payload"] = json.loads(clean_params["payload"])
168
+ except json.JSONDecodeError:
169
+ console.print("[red]Invalid JSON payload[/red]")
170
+ raise typer.Exit(1)
171
+
172
+ # Parse comma-separated protocols
173
+ if "protocols" in clean_params and isinstance(clean_params["protocols"], str):
174
+ clean_params["protocols"] = [p.strip() for p in clean_params["protocols"].split(",")]
175
+
176
+ response = method_func(**clean_params)
177
+
178
+ if "error" in response:
179
+ print_error(response["error"])
180
+
181
+ format_output(response.get("data", response), output_format, filter_path)
@@ -0,0 +1,107 @@
1
+ """Output formatting for the VIBE CLI."""
2
+
3
+ import json
4
+ from typing import Any, Optional
5
+
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+ from rich import print_json
9
+
10
+ console = Console()
11
+
12
+
13
+ def format_output(
14
+ data: Any,
15
+ output_format: str = "table",
16
+ filter_path: Optional[str] = None,
17
+ ) -> None:
18
+ """
19
+ Format and print data in the requested format.
20
+
21
+ Args:
22
+ data: The data to format
23
+ output_format: One of 'table', 'json', 'raw'
24
+ filter_path: Dot-path to extract from data (e.g. 'body.data')
25
+ """
26
+ # Apply filter path
27
+ if filter_path and isinstance(data, dict):
28
+ data = _extract_path(data, filter_path)
29
+
30
+ if output_format == "json":
31
+ if isinstance(data, (dict, list)):
32
+ console.print_json(json.dumps(data, indent=2))
33
+ else:
34
+ console.print_json(json.dumps(data))
35
+ elif output_format == "raw":
36
+ if isinstance(data, (dict, list)):
37
+ console.print(json.dumps(data, indent=2))
38
+ else:
39
+ console.print(str(data))
40
+ else:
41
+ # Table format — auto-detect best rendering
42
+ _print_table(data)
43
+
44
+
45
+ def _extract_path(data: dict, path: str) -> Any:
46
+ """Extract a value from nested dict using dot-path notation."""
47
+ parts = path.split(".")
48
+ current = data
49
+ for part in parts:
50
+ if isinstance(current, dict):
51
+ current = current.get(part)
52
+ elif isinstance(current, list) and part.isdigit():
53
+ idx = int(part)
54
+ current = current[idx] if idx < len(current) else None
55
+ else:
56
+ return None
57
+ return current
58
+
59
+
60
+ def _print_table(data: Any) -> None:
61
+ """Print data as a rich table or fallback to JSON."""
62
+ if isinstance(data, list) and len(data) > 0 and isinstance(data[0], dict):
63
+ # List of dicts → table
64
+ table = Table(show_header=True, header_style="bold cyan")
65
+ columns = list(data[0].keys())
66
+ for col in columns:
67
+ table.add_column(col)
68
+ for row in data:
69
+ table.add_row(*[str(row.get(col, ""))[:80] for col in columns])
70
+ console.print(table)
71
+ elif isinstance(data, dict):
72
+ # Single dict → key-value pairs
73
+ for key, value in data.items():
74
+ if isinstance(value, (dict, list)):
75
+ console.print(f"[bold]{key}:[/bold]")
76
+ console.print_json(json.dumps(value, indent=2))
77
+ else:
78
+ console.print(f"[bold]{key}:[/bold] {value}")
79
+ else:
80
+ console.print(str(data))
81
+
82
+
83
+ def print_error(error: dict) -> None:
84
+ """Print an error response from the gateway."""
85
+ code = error.get("code", "UNKNOWN")
86
+ message = error.get("message", "Unknown error")
87
+ retry_after = error.get("retry_after")
88
+ actions = error.get("actions", [])
89
+
90
+ console.print(f"[bold red]{code}[/bold red]: {message}")
91
+
92
+ if retry_after:
93
+ console.print(f"[dim]Retry after {retry_after}s[/dim]")
94
+
95
+ for action in actions:
96
+ console.print(f"[dim] → {action}[/dim]")
97
+
98
+ # Exit code mapping for agent skill error handling
99
+ import sys
100
+ code_map = {
101
+ "UNAUTHORIZED": 4,
102
+ "FREE_QUOTA_EXHAUSTED": 4,
103
+ "INSUFFICIENT_CREDITS": 4,
104
+ "RATE_LIMITED": 4,
105
+ "FORBIDDEN_SCOPE": 5,
106
+ }
107
+ sys.exit(code_map.get(code, 1))
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.2
2
+ Name: vibe-cli
3
+ Version: 0.1.0
4
+ Summary: VIBE Airforce CLI — Web3 trading and data at your fingertips
5
+ License: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: typer>=0.12.0
9
+ Requires-Dist: httpx>=0.27.0
10
+ Requires-Dist: rich>=13.0.0
11
+ Requires-Dist: pydantic>=2.0.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8.0; extra == "dev"
14
+ Requires-Dist: ruff>=0.4.0; extra == "dev"
15
+
16
+ # vibe-cli
17
+
18
+ Web3 trading and data at your fingertips.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ # pip
24
+ pip install vibe-cli
25
+
26
+ # pipx (isolated)
27
+ pipx install vibe-cli
28
+
29
+ # curl | sh
30
+ curl -fsSL https://api.vibe.airforce/api/cli/install.sh | sh
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ ```bash
36
+ vibe sync # Fetch operations manifest (works without API key)
37
+ vibe list-operations # List all commands
38
+ vibe swap-quote --from-token So11111111111111111111111111111111111111112 \
39
+ --to-token EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v \
40
+ --amount 1000000
41
+ vibe evm-swap-quote --network base --from-asset eth --to-asset usdc --amount 0.1
42
+ ```
43
+
44
+ ## Auth
45
+
46
+ No API key required to try — 30 free credits per day.
47
+
48
+ ```bash
49
+ vibe auth # Paste your API key interactively
50
+ # Or set environment variable
51
+ export VIBE_API_KEY=pk_xxx:sk_xxx
52
+ ```
53
+
54
+ Get your API key at [vibe.airforce/settings/api-keys](https://www.vibe.airforce/settings/api-keys)
55
+
56
+ ## Commands
57
+
58
+ | Command | Description |
59
+ |---------|-------------|
60
+ | `vibe sync` | Fetch operations manifest |
61
+ | `vibe list-operations` | List available commands |
62
+ | `vibe auth` | Authenticate with API key |
63
+ | `vibe logout` | Remove saved credentials |
64
+ | `vibe swap-quote` | Solana swap quote |
65
+ | `vibe swap` | Execute Solana swap |
66
+ | `vibe evm-swap-quote` | Base/Ethereum swap quote |
67
+ | `vibe evm-swap` | Execute Base/Ethereum swap |
68
+ | `vibe defi-discover` | Discover DeFi protocols |
69
+ | `vibe defi-quote` | Get DeFi position quote |
70
+ | `vibe defi-deposit` | Execute DeFi deposit |
71
+ | `vibe bags-launch-token` | Launch token on bags.fm |
72
+ | `vibe bags-claim-fees` | Claim bags.fm fees |
73
+ | `vibe bags-positions` | View bags.fm positions |
74
+ | `vibe data-provider` | Query external data providers |
75
+ | `vibe feedback` | Submit feedback |
76
+ | `vibe version` | Show CLI version |
77
+
78
+ ## Output Formats
79
+
80
+ ```bash
81
+ vibe swap-quote -o table ... # Human-readable (default)
82
+ vibe swap-quote -o json ... # Structured JSON
83
+ vibe swap-quote -o json -f body.data ... # Extract nested field
84
+ vibe swap-quote -o raw ... # Unformatted JSON
85
+ ```
86
+
87
+ ## License
88
+
89
+ MIT
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ vibe_cli/__init__.py
4
+ vibe_cli/auth.py
5
+ vibe_cli/client.py
6
+ vibe_cli/config.py
7
+ vibe_cli/main.py
8
+ vibe_cli/operations.py
9
+ vibe_cli/output.py
10
+ vibe_cli.egg-info/PKG-INFO
11
+ vibe_cli.egg-info/SOURCES.txt
12
+ vibe_cli.egg-info/dependency_links.txt
13
+ vibe_cli.egg-info/entry_points.txt
14
+ vibe_cli.egg-info/requires.txt
15
+ vibe_cli.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ vibe = vibe_cli.main:app
@@ -0,0 +1,8 @@
1
+ typer>=0.12.0
2
+ httpx>=0.27.0
3
+ rich>=13.0.0
4
+ pydantic>=2.0.0
5
+
6
+ [dev]
7
+ pytest>=8.0
8
+ ruff>=0.4.0
@@ -0,0 +1 @@
1
+ vibe_cli