hevn-cli 0.1.2__tar.gz → 0.1.3__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.
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/PKG-INFO +7 -8
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/README.md +4 -5
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/pyproject.toml +3 -3
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/banks.py +64 -67
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/cards.py +95 -92
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/contracts.py +29 -28
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/invoices.py +1 -4
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/transfer.py +23 -22
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/res/CLAUDE.md +2 -9
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/LICENSE +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/__init__.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/api/__init__.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/api/app.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/api/base.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/api/public.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/client.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/command_meta.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/__init__.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/account.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/auth.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/balance.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/contacts.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/contractors.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/mcp.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/commands/status.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/config.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/env.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/errors.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/formatters/__init__.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/formatters/invoices.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/interactivity.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/main.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/manifest.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/mcp/__init__.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/mcp/first_run.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/mcp/install.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/mcp/server.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/mcp/tools.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/normalize.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/options.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/output.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/parsing.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/progress.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/prompts.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/render.py +0 -0
- {hevn_cli-0.1.2 → hevn_cli-0.1.3}/src/hevn_cli/res/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hevn-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Command-line client for the HEVN backend API and MCP transfers.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -31,8 +31,8 @@ Requires-Dist: questionary (==2.1.0)
|
|
|
31
31
|
Requires-Dist: rich (==13.9.4)
|
|
32
32
|
Requires-Dist: tomli-w (>=1.0,<2)
|
|
33
33
|
Requires-Dist: typer[all] (==0.15.1)
|
|
34
|
-
Project-URL: Homepage, https://
|
|
35
|
-
Project-URL: Repository, https://github.com/hevn/hevn-cli
|
|
34
|
+
Project-URL: Homepage, https://gethevn.com
|
|
35
|
+
Project-URL: Repository, https://github.com/hevn-inc/hevn-cli
|
|
36
36
|
Description-Content-Type: text/markdown
|
|
37
37
|
|
|
38
38
|
# HEVN CLI
|
|
@@ -70,7 +70,7 @@ hevn --help
|
|
|
70
70
|
Homebrew install, after a tap is published:
|
|
71
71
|
|
|
72
72
|
```bash
|
|
73
|
-
brew tap hevn/
|
|
73
|
+
brew tap hevn-inc/tap
|
|
74
74
|
brew install hevn-cli
|
|
75
75
|
hevn --help
|
|
76
76
|
```
|
|
@@ -84,7 +84,7 @@ class HevnCli < Formula
|
|
|
84
84
|
include Language::Python::Virtualenv
|
|
85
85
|
|
|
86
86
|
desc "Command-line client for the HEVN backend API and MCP transfers"
|
|
87
|
-
homepage "https://
|
|
87
|
+
homepage "https://gethevn.com"
|
|
88
88
|
url "https://files.pythonhosted.org/packages/source/h/hevn-cli/hevn_cli-0.1.0.tar.gz"
|
|
89
89
|
sha256 "<sha256>"
|
|
90
90
|
license "MIT"
|
|
@@ -107,11 +107,10 @@ class HevnCli < Formula
|
|
|
107
107
|
end
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
|
|
111
|
-
can fetch newer commits:
|
|
110
|
+
Install with `pipx` from PyPI:
|
|
112
111
|
|
|
113
112
|
```bash
|
|
114
|
-
pipx install
|
|
113
|
+
pipx install hevn-cli
|
|
115
114
|
pipx upgrade hevn-cli
|
|
116
115
|
```
|
|
117
116
|
|
|
@@ -33,7 +33,7 @@ hevn --help
|
|
|
33
33
|
Homebrew install, after a tap is published:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
brew tap hevn/
|
|
36
|
+
brew tap hevn-inc/tap
|
|
37
37
|
brew install hevn-cli
|
|
38
38
|
hevn --help
|
|
39
39
|
```
|
|
@@ -47,7 +47,7 @@ class HevnCli < Formula
|
|
|
47
47
|
include Language::Python::Virtualenv
|
|
48
48
|
|
|
49
49
|
desc "Command-line client for the HEVN backend API and MCP transfers"
|
|
50
|
-
homepage "https://
|
|
50
|
+
homepage "https://gethevn.com"
|
|
51
51
|
url "https://files.pythonhosted.org/packages/source/h/hevn-cli/hevn_cli-0.1.0.tar.gz"
|
|
52
52
|
sha256 "<sha256>"
|
|
53
53
|
license "MIT"
|
|
@@ -70,11 +70,10 @@ class HevnCli < Formula
|
|
|
70
70
|
end
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
can fetch newer commits:
|
|
73
|
+
Install with `pipx` from PyPI:
|
|
75
74
|
|
|
76
75
|
```bash
|
|
77
|
-
pipx install
|
|
76
|
+
pipx install hevn-cli
|
|
78
77
|
pipx upgrade hevn-cli
|
|
79
78
|
```
|
|
80
79
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "hevn-cli"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.3"
|
|
4
4
|
description = "Command-line client for the HEVN backend API and MCP transfers."
|
|
5
5
|
authors = ["HEVN Team <team@hevn.finance>"]
|
|
6
6
|
license = "MIT"
|
|
7
7
|
readme = "README.md"
|
|
8
|
-
homepage = "https://
|
|
9
|
-
repository = "https://github.com/hevn/hevn-cli"
|
|
8
|
+
homepage = "https://gethevn.com"
|
|
9
|
+
repository = "https://github.com/hevn-inc/hevn-cli"
|
|
10
10
|
keywords = ["hevn", "cli", "fintech", "payments", "invoicing", "mcp", "stablecoin"]
|
|
11
11
|
classifiers = [
|
|
12
12
|
"Development Status :: 4 - Beta",
|
|
@@ -120,37 +120,37 @@ def list_banks(
|
|
|
120
120
|
yaml_output: YamlOpt = False,
|
|
121
121
|
) -> None:
|
|
122
122
|
data = AppApi().list_banks()
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
123
|
+
|
|
124
|
+
def _render(_: dict) -> None:
|
|
125
|
+
banks = data.get("allOptions") or [] if all_options else data.get("banks", [])
|
|
126
|
+
title = "Bank Options" if all_options else "Banks"
|
|
127
|
+
table = Table(title=f"{title} ({len(banks)})", box=box.SIMPLE_HEAVY)
|
|
128
|
+
table.add_column("ID", overflow="fold")
|
|
129
|
+
table.add_column("Bank")
|
|
130
|
+
table.add_column("Country")
|
|
131
|
+
table.add_column("Currency")
|
|
132
|
+
table.add_column("Method")
|
|
133
|
+
table.add_column("Status")
|
|
134
|
+
table.add_column("Account Holder")
|
|
135
|
+
table.add_column("BIN")
|
|
136
|
+
table.add_column("Routing Number")
|
|
137
|
+
table.add_column("IBAN")
|
|
138
|
+
for bank in banks:
|
|
139
|
+
table.add_row(
|
|
140
|
+
str(bank.get("id") or "-"),
|
|
141
|
+
bank.get("bankName") or "-",
|
|
142
|
+
bank.get("country") or "-",
|
|
143
|
+
bank.get("currency") or "-",
|
|
144
|
+
str(bank.get("method") or "-"),
|
|
145
|
+
str(bank.get("status") or "-"),
|
|
146
|
+
_account_holder(bank),
|
|
147
|
+
_bank_bin(bank),
|
|
148
|
+
_routing_number(bank),
|
|
149
|
+
_account_summary(bank),
|
|
150
|
+
)
|
|
151
|
+
console.print(table)
|
|
152
|
+
|
|
153
|
+
emit(data, OutputOptions(raw_json, yaml_output), _render)
|
|
154
154
|
|
|
155
155
|
|
|
156
156
|
@app.command("details", help="Show details for a single bank rail.")
|
|
@@ -162,39 +162,39 @@ def bank_details(
|
|
|
162
162
|
rail = rail or typer.prompt("Bank rail").strip()
|
|
163
163
|
data = AppApi().list_banks()
|
|
164
164
|
bank = _find_bank_by_rail(data, rail)
|
|
165
|
-
if raw_json or yaml_output:
|
|
166
|
-
emit(bank, OutputOptions(raw_json, yaml_output))
|
|
167
|
-
return
|
|
168
165
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
166
|
+
def _render(_: dict) -> None:
|
|
167
|
+
iban_data = bank.get("ibanData") or {}
|
|
168
|
+
lines = [
|
|
169
|
+
f"Rail: [bold]{bank.get('rail') or '-'}[/bold]",
|
|
170
|
+
f"Bank: {bank.get('bankName') or '-'}",
|
|
171
|
+
f"Country: {bank.get('country') or '-'}",
|
|
172
|
+
f"Currency: {bank.get('currency') or '-'}",
|
|
173
|
+
f"Method: {bank.get('method') or '-'}",
|
|
174
|
+
f"Status: [bold]{bank.get('status') or '-'}[/bold]",
|
|
175
|
+
f"Pooled: {'yes' if bank.get('pooled') else 'no'}",
|
|
176
|
+
f"Compliance: {bank.get('compliance') if bank.get('compliance') is not None else '-'}",
|
|
177
|
+
f"Deposit fee: {bank.get('depositFee') if bank.get('depositFee') is not None else '-'}",
|
|
178
|
+
f"Time to open: {bank.get('timeToOpen') or '-'}",
|
|
179
|
+
f"Account: {_account_summary(bank)}",
|
|
180
|
+
f"External account ID: {bank.get('externalAccountId') or '-'}",
|
|
181
|
+
]
|
|
182
|
+
if iban_data:
|
|
183
|
+
holder = iban_data.get("accountHolderBusinessName") or " ".join(
|
|
184
|
+
part
|
|
185
|
+
for part in [
|
|
186
|
+
iban_data.get("accountHolderFirstName"),
|
|
187
|
+
iban_data.get("accountHolderLastName"),
|
|
188
|
+
]
|
|
189
|
+
if part
|
|
190
|
+
)
|
|
191
|
+
if holder:
|
|
192
|
+
lines.append(f"Account holder: {holder}")
|
|
193
|
+
if iban_data.get("paymentReference"):
|
|
194
|
+
lines.append(f"Payment reference: [bold yellow]{iban_data['paymentReference']}[/bold yellow]")
|
|
195
|
+
console.print(Panel.fit("\n".join(lines), title="Bank Details", border_style="cyan"))
|
|
196
|
+
|
|
197
|
+
emit(bank, OutputOptions(raw_json, yaml_output), _render)
|
|
198
198
|
|
|
199
199
|
|
|
200
200
|
@app.command("validate", help="Validate bank requisites (ACH/SEPA/SWIFT/UAEFTS) before creating a contact.")
|
|
@@ -228,7 +228,4 @@ def validate_bank_details(
|
|
|
228
228
|
"routingNumber": routing_number,
|
|
229
229
|
}
|
|
230
230
|
)
|
|
231
|
-
|
|
232
|
-
emit(data, OutputOptions(raw_json, yaml_output))
|
|
233
|
-
return
|
|
234
|
-
_print_validation_result(data)
|
|
231
|
+
emit(data, OutputOptions(raw_json, yaml_output), lambda _: _print_validation_result(data))
|
|
@@ -135,30 +135,30 @@ def list_cards(
|
|
|
135
135
|
"kycStatus": data.get("kycStatus"),
|
|
136
136
|
"phoneVerified": data.get("phoneVerified"),
|
|
137
137
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
138
|
+
def _render(_: dict) -> None:
|
|
139
|
+
if not data.get("cards"):
|
|
140
|
+
print_cards_status(data)
|
|
141
|
+
print_kyc_prompt(no_qr=no_qr)
|
|
142
|
+
return
|
|
143
|
+
table = Table(title=f"Cards ({data.get('total', 0)})", box=box.SIMPLE_HEAVY)
|
|
144
|
+
table.add_column("ID", overflow="fold")
|
|
145
|
+
table.add_column("Details")
|
|
146
|
+
table.add_column("Daily")
|
|
147
|
+
table.add_column("Activated")
|
|
148
|
+
for card in data.get("cards", []):
|
|
149
|
+
table.add_row(
|
|
150
|
+
str(card.get("id")),
|
|
151
|
+
card_details(card),
|
|
152
|
+
format_limit(card.get("limit")),
|
|
153
|
+
"yes" if card.get("activated") else "no",
|
|
154
|
+
)
|
|
155
|
+
console.print(table)
|
|
156
|
+
console.print(
|
|
157
|
+
f"KYC: [bold]{data.get('kycStatus')}[/bold] "
|
|
158
|
+
f"Phone verified: [bold]{'yes' if data.get('phoneVerified') else 'no'}[/bold]"
|
|
156
159
|
)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
f"KYC: [bold]{data.get('kycStatus')}[/bold] "
|
|
160
|
-
f"Phone verified: [bold]{'yes' if data.get('phoneVerified') else 'no'}[/bold]"
|
|
161
|
-
)
|
|
160
|
+
|
|
161
|
+
emit(wrapped, OutputOptions(raw_json, yaml_output), _render)
|
|
162
162
|
|
|
163
163
|
|
|
164
164
|
@app.command("status", help="Show card program status and KYC requirements.")
|
|
@@ -186,21 +186,21 @@ def pre_approve_cards(
|
|
|
186
186
|
no_qr: bool = typer.Option(False, "--no-qr", help="Do not print terminal QR code."),
|
|
187
187
|
) -> None:
|
|
188
188
|
data = AppApi().pre_approve_cards()
|
|
189
|
-
if raw_json or yaml_output:
|
|
190
|
-
emit(data, OutputOptions(raw_json, yaml_output))
|
|
191
|
-
return
|
|
192
189
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
190
|
+
def _render(_: dict) -> None:
|
|
191
|
+
lines = [
|
|
192
|
+
f"KYC status: [bold]{data.get('kycStatus') or '-'}[/bold]",
|
|
193
|
+
f"Requires phone: [bold]{'yes' if data.get('requirePhoneNumber') else 'no'}[/bold]",
|
|
194
|
+
f"Requires address: [bold]{'yes' if data.get('requireAddress') else 'no'}[/bold]",
|
|
195
|
+
]
|
|
196
|
+
console.print(Panel.fit("\n".join(lines), title="Card Authorization", border_style="cyan"))
|
|
197
|
+
link = data.get("kycUrl")
|
|
198
|
+
if link:
|
|
199
|
+
print_link_qr("KYC Link", link, show_qr=not no_qr)
|
|
200
|
+
else:
|
|
201
|
+
console.print("[yellow]No KYC link returned.[/yellow]")
|
|
202
|
+
|
|
203
|
+
emit(data, OutputOptions(raw_json, yaml_output), _render)
|
|
204
204
|
|
|
205
205
|
|
|
206
206
|
@app.command("kyc-link", help="Get the cardholder KYC verification link.")
|
|
@@ -210,13 +210,14 @@ def card_kyc_link(
|
|
|
210
210
|
no_qr: bool = typer.Option(False, "--no-qr", help="Do not print terminal QR code."),
|
|
211
211
|
) -> None:
|
|
212
212
|
data = AppApi().card_kyc_link()
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
213
|
+
|
|
214
|
+
def _render(_: dict) -> None:
|
|
215
|
+
link = data.get("link")
|
|
216
|
+
if not link:
|
|
217
|
+
raise HevnError("KYC link was not returned.", error_code='INTERNAL_ERROR')
|
|
218
|
+
print_link_qr("KYC Link", link, show_qr=not no_qr)
|
|
219
|
+
|
|
220
|
+
emit(data, OutputOptions(raw_json, yaml_output), _render)
|
|
220
221
|
|
|
221
222
|
|
|
222
223
|
@app.command("issue", hidden=True)
|
|
@@ -279,23 +280,23 @@ def create_cards(
|
|
|
279
280
|
created.append(_create_one_card(card_format, card_label))
|
|
280
281
|
|
|
281
282
|
result = {"cards": created, "total": len(created)}
|
|
282
|
-
if raw_json or yaml_output:
|
|
283
|
-
emit(result, OutputOptions(raw_json, yaml_output))
|
|
284
|
-
return
|
|
285
283
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
284
|
+
def _render(_: dict) -> None:
|
|
285
|
+
table = Table(title=f"Created Cards ({len(created)})", box=box.SIMPLE_HEAVY)
|
|
286
|
+
table.add_column("ID", overflow="fold")
|
|
287
|
+
table.add_column("Details")
|
|
288
|
+
table.add_column("Label")
|
|
289
|
+
table.add_column("Status")
|
|
290
|
+
for card in created:
|
|
291
|
+
table.add_row(
|
|
292
|
+
str(card.get("id")),
|
|
293
|
+
card_details(card),
|
|
294
|
+
card.get("label") or "-",
|
|
295
|
+
str(card.get("status") or "-"),
|
|
296
|
+
)
|
|
297
|
+
console.print(table)
|
|
298
|
+
|
|
299
|
+
emit(result, OutputOptions(raw_json, yaml_output), _render)
|
|
299
300
|
|
|
300
301
|
|
|
301
302
|
@app.command("get", help="Show a card's details.")
|
|
@@ -308,28 +309,29 @@ def get_card(
|
|
|
308
309
|
) -> None:
|
|
309
310
|
card_id = _card_id(ctx, card_id_option or card_id)
|
|
310
311
|
data = AppApi().get_card(card_id)
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
312
|
+
|
|
313
|
+
def _render(_: dict) -> None:
|
|
314
|
+
card_data = data.get("cardData") or {}
|
|
315
|
+
billing = data.get("billingAddress") or {}
|
|
316
|
+
console.print(
|
|
317
|
+
Panel.fit(
|
|
318
|
+
f"ID: [bold]{data.get('id')}[/bold]\n"
|
|
319
|
+
f"Name: {card_title(data)}\n"
|
|
320
|
+
f"Last 4: {card_last_four(data)}\n"
|
|
321
|
+
f"Status: {data.get('status') or '-'}\n"
|
|
322
|
+
f"Payment system: {card_data.get('paymentSystem') or '-'}\n"
|
|
323
|
+
f"Expiry: {card_data.get('expiryDate') or '-'}\n"
|
|
324
|
+
f"Daily limit: {format_limit(data.get('limit'))}\n"
|
|
325
|
+
f"Daily spend: {money(data.get('dailySpend'), 'USD')}\n"
|
|
326
|
+
f"Activated: {'yes' if data.get('activated') else 'no'}\n"
|
|
327
|
+
f"Wallet: {data.get('walletAddress') or '-'}\n"
|
|
328
|
+
f"Billing country: {billing.get('country') or '-'}",
|
|
329
|
+
title="Card Details",
|
|
330
|
+
border_style="cyan",
|
|
331
|
+
)
|
|
331
332
|
)
|
|
332
|
-
|
|
333
|
+
|
|
334
|
+
emit(data, OutputOptions(raw_json, yaml_output), _render)
|
|
333
335
|
|
|
334
336
|
|
|
335
337
|
@app.command("reveal", help="Reveal sensitive card details (number, CVV, expiry) using a wallet signature.")
|
|
@@ -347,17 +349,18 @@ def reveal_card(
|
|
|
347
349
|
if nonce is None:
|
|
348
350
|
nonce = int(typer.prompt("Nonce").strip())
|
|
349
351
|
data = AppApi().card_details(card_id, {"messageSignature": signature, "nonce": nonce})
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
352
|
+
emit(
|
|
353
|
+
data,
|
|
354
|
+
OutputOptions(raw_json, yaml_output),
|
|
355
|
+
lambda _: console.print(
|
|
356
|
+
Panel.fit(
|
|
357
|
+
f"Card number: [bold]{data.get('cardNumber')}[/bold]\n"
|
|
358
|
+
f"CVV: [bold]{data.get('cvv')}[/bold]\n"
|
|
359
|
+
f"Expiry: {data.get('expiryDate') or '-'}",
|
|
360
|
+
title="Sensitive Card Details",
|
|
361
|
+
border_style="yellow",
|
|
362
|
+
)
|
|
363
|
+
),
|
|
361
364
|
)
|
|
362
365
|
|
|
363
366
|
|
|
@@ -310,34 +310,35 @@ def get_contract(
|
|
|
310
310
|
contract_id = _contract_id(ctx, contract_id_option or contract_id)
|
|
311
311
|
api = AppApi()
|
|
312
312
|
data = _with_income(api.get_contract(contract_id), str(api.current_user().get("id") or ""))
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
313
|
+
|
|
314
|
+
def _render(_: dict[str, Any]) -> None:
|
|
315
|
+
# Contract-level metadata (the rest of the substance lives in `fields`).
|
|
316
|
+
meta_keys = ("label", "status", "period", "activationAt", "expirationAt", "documentId", "paymentMethods")
|
|
317
|
+
meta = Table(box=None, show_header=False, pad_edge=False)
|
|
318
|
+
meta.add_column(style="bold", no_wrap=True)
|
|
319
|
+
meta.add_column(overflow="fold")
|
|
320
|
+
for key in meta_keys:
|
|
321
|
+
meta.add_row(key, _render_field_value(data.get(key)))
|
|
322
|
+
|
|
323
|
+
# Show every USER field the schema declares for this type — even empty ones —
|
|
324
|
+
# plus any extra stored fields, so the full expected set is visible.
|
|
325
|
+
fields = data.get("fields") or {}
|
|
326
|
+
template = next((t for t in api.list_contract_templates().get("templates", []) if t.get("type") == data.get("type")), None)
|
|
327
|
+
declared = [f["key"] for f in (template or {}).get("fields", []) if f.get("source") == "user" and f.get("key")]
|
|
328
|
+
ordered_keys = list(dict.fromkeys([*declared, *fields.keys()]))
|
|
329
|
+
|
|
330
|
+
body: list[RenderableType] = [meta]
|
|
331
|
+
if ordered_keys:
|
|
332
|
+
fields_table = Table(title="Fields", title_justify="left", box=box.SIMPLE_HEAVY)
|
|
333
|
+
fields_table.add_column("Field", no_wrap=True)
|
|
334
|
+
fields_table.add_column("Value", overflow="fold")
|
|
335
|
+
for key in ordered_keys:
|
|
336
|
+
fields_table.add_row(str(key), _render_field_value(fields.get(key)))
|
|
337
|
+
body.append(fields_table)
|
|
338
|
+
|
|
339
|
+
console.print(Panel(Group(*body), title=f"Contract {contract_id}", border_style="cyan"))
|
|
340
|
+
|
|
341
|
+
emit(data, OutputOptions(raw_json, yaml_output), _render)
|
|
341
342
|
|
|
342
343
|
|
|
343
344
|
def _render_field_value(value: Any) -> RenderableType:
|
|
@@ -492,10 +492,7 @@ def get_invoice(
|
|
|
492
492
|
) -> None:
|
|
493
493
|
invoice_id = invoice_id_option or invoice_id or typer.prompt("Invoice id").strip()
|
|
494
494
|
data = PublicApi().invoice(invoice_id) if public else AppApi().get_invoice(invoice_id)
|
|
495
|
-
|
|
496
|
-
emit(data, OutputOptions(raw_json, yaml_output))
|
|
497
|
-
return
|
|
498
|
-
print_invoice_panel(invoice_id, data)
|
|
495
|
+
emit(data, OutputOptions(raw_json, yaml_output), lambda _: print_invoice_panel(invoice_id, data))
|
|
499
496
|
|
|
500
497
|
|
|
501
498
|
@app.command("decline", help="Decline an incoming invoice.")
|
|
@@ -475,25 +475,26 @@ def list_transfers(
|
|
|
475
475
|
) -> None:
|
|
476
476
|
data = AppApi().list_transactions(limit=limit, offset=offset)
|
|
477
477
|
data = _mark_mcp_transactions(data, _mcp_wallet())
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
)
|
|
499
|
-
|
|
478
|
+
|
|
479
|
+
def _render(_: dict[str, Any]) -> None:
|
|
480
|
+
table = Table(title="Transactions", box=box.SIMPLE_HEAVY)
|
|
481
|
+
table.add_column("Time")
|
|
482
|
+
table.add_column("Type")
|
|
483
|
+
table.add_column("Amount")
|
|
484
|
+
table.add_column("Counterparty")
|
|
485
|
+
table.add_column("Status")
|
|
486
|
+
table.add_column("MCP")
|
|
487
|
+
table.add_column("Tx")
|
|
488
|
+
for row in data.get("transactions", []):
|
|
489
|
+
table.add_row(
|
|
490
|
+
row.get("time", "-"),
|
|
491
|
+
str(row.get("type") or "-"),
|
|
492
|
+
_tx_amount(row),
|
|
493
|
+
_tx_counterparty(row),
|
|
494
|
+
str(row.get("status") or "-"),
|
|
495
|
+
"true" if row.get("mcp") else "",
|
|
496
|
+
str(row.get("txHash") or row.get("onchainTransactionId") or "-"),
|
|
497
|
+
)
|
|
498
|
+
console.print(table)
|
|
499
|
+
|
|
500
|
+
emit(data, OutputOptions(raw_json, yaml_output), _render)
|
|
@@ -46,15 +46,8 @@ poetry run hevn --help
|
|
|
46
46
|
|
|
47
47
|
If Poetry is configured without virtualenv creation, use a clean virtualenv or CI before release checks.
|
|
48
48
|
|
|
49
|
-
For a user-facing `pipx` install
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
pipx install "git+https://github.com/hevn/hevn-cli.git"
|
|
54
|
-
pipx upgrade hevn-cli
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
After PyPI publication, use:
|
|
49
|
+
For a user-facing `pipx` install, use PyPI (the repository is private, so
|
|
50
|
+
GitHub VCS specifiers are not an option):
|
|
58
51
|
|
|
59
52
|
```bash
|
|
60
53
|
pipx install hevn-cli
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|