mayar 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # mayar
2
2
 
3
- Command-line interface for the [Mayar](https://docs.mayar.id) API. Production-only (`https://api.mayar.id`). Zero runtime dependencies — Node.js 18+ stdlib only.
3
+ Command-line interface for the [Mayar](https://docs.mayar.id) API. Zero runtime dependencies — Node.js 18+ stdlib only.
4
4
 
5
5
  ## Install
6
6
 
@@ -18,21 +18,42 @@ cd mayar-cli
18
18
  npm link # exposes a `mayar` command on your PATH
19
19
  ```
20
20
 
21
- ## First run
21
+ ## Authentication
22
22
 
23
- The first time you invoke any command, the CLI prints an ASCII banner and asks for your production API key (input is masked). Paste it once; it's stored at `~/.config/mayar/config.json` (chmod 600, follows the [XDG Base Directory](https://specification.freedesktop.org/basedir-spec/latest/) spec) and reused on subsequent runs.
23
+ Resolution order: `--api-key` flag `MAYAR_API_KEY` env saved config file.
24
+
25
+ **Option 1 — environment variable (recommended for CI/agents):**
26
+
27
+ ```bash
28
+ export MAYAR_API_KEY=your_api_key_here
29
+ mayar whoami
30
+ ```
31
+
32
+ **Option 2 — saved config (interactive first run):**
33
+
34
+ The first time you invoke any command without a key configured, the CLI prompts for your production API key (input is masked). Paste it once; it's stored at `~/.config/mayar/config.json` (chmod 600, follows [XDG Base Directory](https://specification.freedesktop.org/basedir-spec/latest/)) and reused on subsequent runs.
24
35
 
25
36
  ```bash
26
37
  mayar balance
27
- # ███╗ ███╗ █████╗ ██╗ ██╗ █████╗ ██████╗
28
- # ...
29
38
  # Welcome to Mayar CLI.
30
39
  # No API key found. Get yours from https://web.mayar.id → Integration → API Key.
31
40
  # Paste your production API key: ************
32
41
  # ✓ Saved to /Users/you/.config/mayar/config.json
33
42
  ```
34
43
 
35
- You can also run `mayar init` explicitly to (re-)configure the key, or pass `--api-key <key>` on any invocation to override.
44
+ **Option 3 non-interactive setup:**
45
+
46
+ ```bash
47
+ mayar api-key YOUR_KEY_HERE
48
+ ```
49
+
50
+ **Option 4 — per-command override:**
51
+
52
+ ```bash
53
+ mayar --api-key YOUR_KEY_HERE balance
54
+ ```
55
+
56
+ Get your API key from [web.mayar.id](https://web.mayar.id) → Integration → API Key.
36
57
 
37
58
  ## Commands
38
59
 
@@ -40,11 +61,11 @@ You can also run `mayar init` explicitly to (re-)configure the key, or pass `--a
40
61
  Setup
41
62
  init Run first-time setup (interactive, masked input)
42
63
  api-key <key> Save API key non-interactively (e.g. for scripts)
43
- config show Show masked saved key
64
+ config show Show masked saved key and config path
44
65
  config reset Remove the saved key
45
66
 
46
67
  Account
47
- whoami Show merchant identity (decoded from JWT key) + verify
68
+ whoami Show merchant identity (decoded from JWT) + verify key
48
69
  balance GET /hl/v1/balance
49
70
 
50
71
  Invoices
@@ -72,10 +93,17 @@ Single payment requests
72
93
  Customers
73
94
  customer list [--page ...] GET /hl/v1/customer
74
95
  customer create --data <json|@file> POST /hl/v1/customer/create
96
+ customer search <email> GET /hl/v1/customer/detail?email=
97
+ customer update <from> <to> POST /hl/v1/customer/update
98
+ customer magic-link <email> POST /hl/v1/customer/login/portal
75
99
 
76
100
  Transactions
77
101
  tx list [--page ...] GET /hl/v1/transactions (paid)
78
102
  tx unpaid [--page ...] GET /hl/v1/transactions/unpaid
103
+ tx daily GET /hl/v1/transactions/daily
104
+
105
+ Reviews
106
+ review list [--page ...] GET /hl/v1/reviews
79
107
 
80
108
  Dynamic QR
81
109
  qrcode <amount> POST /hl/v1/qrcode/create
@@ -94,14 +122,16 @@ Global flags
94
122
  -v, --version Show version
95
123
 
96
124
  Environment
97
- MAYAR_API_KEY Used when --api-key is not given and no config is saved
125
+ MAYAR_API_KEY API key — used when --api-key flag is absent and no config is saved
126
+ MAYAR_API_URL Override API base URL (default: https://api.mayar.id)
98
127
  ```
99
128
 
100
- Resolution order: `--api-key` flag → `MAYAR_API_KEY` env → saved config.
101
-
102
129
  ## Examples
103
130
 
104
131
  ```bash
132
+ # Verify active user / API key
133
+ mayar whoami
134
+
105
135
  # Account balance
106
136
  mayar balance
107
137
 
@@ -146,10 +176,10 @@ mayar invoice list --json | jq '.data[] | {id, status}'
146
176
 
147
177
  ## Config
148
178
 
149
- | Key | Value |
150
- | -------- | ------------------------------------------ |
179
+ | Key | Value |
180
+ | -------- | ------------------------------------------------------------------------------------------- |
151
181
  | Path | `$XDG_CONFIG_HOME/mayar/config.json`, defaulting to `~/.config/mayar/config.json` (chmod 600) |
152
- | Endpoint | `https://api.mayar.id` (production, fixed) |
182
+ | Endpoint | `MAYAR_API_URL` env or `https://api.mayar.id` (default) |
153
183
 
154
184
  Legacy installs that wrote to `~/.mayar/config.json` are migrated automatically on first run.
155
185
 
@@ -157,6 +187,7 @@ To rotate keys: `mayar config reset && mayar init`.
157
187
 
158
188
  ## Notes
159
189
 
160
- - Sandbox is intentionally not supported — this CLI hits production only.
161
190
  - All requests use `Authorization: Bearer <key>`. Errors print `API <status> — <message>` and exit non-zero.
162
191
  - `--data @file.json` reads from disk; `--data '{...}'` reads inline JSON.
192
+ - `MAYAR_API_URL` overrides the base URL — useful for staging or custom proxy environments.
193
+ - `mayar whoami` decodes the JWT locally and verifies the key live against `/hl/v1/balance`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mayar",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Zero-dependency Node.js CLI for the Mayar API (production endpoint).",
5
5
  "bin": {
6
6
  "mayar": "bin/mayar.js"
package/src/api.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const https = require('https');
2
2
  const { URL } = require('url');
3
3
 
4
- const BASE_URL = 'https://api.mayar.id';
4
+ const BASE_URL = process.env.MAYAR_API_URL || 'https://api.mayar.id';
5
5
  const VERSION = require('../package.json').version;
6
6
 
7
7
  function request(method, pathname, { apiKey, body, query } = {}) {
package/src/cli.js CHANGED
@@ -43,10 +43,17 @@ ${ui.bold('Single payment requests:')}
43
43
  ${ui.bold('Customers:')}
44
44
  customer list [--page N --pageSize N]
45
45
  customer create --data <json|@file.json>
46
+ customer search <email> Look up a customer by email
47
+ customer update <fromEmail> <toEmail>
48
+ customer magic-link <email> Email a portal login link to the customer
46
49
 
47
50
  ${ui.bold('Transactions:')}
48
51
  tx list [--page N --pageSize N] Paid transactions
49
52
  tx unpaid [--page N --pageSize N] Unpaid transactions
53
+ tx daily Today's totals (volume + count)
54
+
55
+ ${ui.bold('Reviews:')}
56
+ review list [--page N --pageSize N]
50
57
 
51
58
  ${ui.bold('Dynamic QR:')}
52
59
  qrcode <amount>
@@ -66,9 +73,10 @@ ${ui.bold('Global flags:')}
66
73
 
67
74
  ${ui.bold('Environment:')}
68
75
  MAYAR_API_KEY Used when --api-key is not given and no config is saved
76
+ MAYAR_API_URL Override API base URL (default: https://api.mayar.id)
69
77
 
70
78
  ${ui.dim('Resolution order: --api-key flag > MAYAR_API_KEY env > saved config.')}
71
- ${ui.dim('Endpoint: https://api.mayar.id (production only)')}
79
+ ${ui.dim('Endpoint: MAYAR_API_URL env or https://api.mayar.id (default)')}
72
80
  ${ui.dim('Config: ~/.config/mayar/config.json (chmod 600)')}
73
81
  `;
74
82
 
@@ -169,6 +177,8 @@ async function run(argv) {
169
177
  qr: './commands/qrcode',
170
178
  qrcode: './commands/qrcode',
171
179
  webhook: './commands/webhook',
180
+ review: './commands/review',
181
+ reviews: './commands/review',
172
182
  };
173
183
  const handler = handlers[cmd];
174
184
  if (!handler) {
@@ -2,10 +2,10 @@ const api = require('../api');
2
2
  const ui = require('../ui');
3
3
  const { checkResp, readData } = require('../util');
4
4
 
5
- const USAGE = 'Usage: mayar customer <list|create>';
5
+ const USAGE = 'Usage: mayar customer <list|create|search|update|magic-link>';
6
6
 
7
7
  async function run({ apiKey, flags, positional }) {
8
- const [sub] = positional;
8
+ const [sub, ...rest] = positional;
9
9
  switch (sub) {
10
10
  case 'list': {
11
11
  const res = await api.request('GET', '/hl/v1/customer', {
@@ -26,6 +26,28 @@ async function run({ apiKey, flags, positional }) {
26
26
  const res = await api.request('POST', '/hl/v1/customer/create', { apiKey, body });
27
27
  checkResp(res); ui.jsonOut(res.body); return;
28
28
  }
29
+ case 'search': {
30
+ if (!rest[0]) throw new Error('Usage: mayar customer search <email>');
31
+ const res = await api.request('GET', '/hl/v1/customer/detail', {
32
+ apiKey, query: { email: rest[0] },
33
+ });
34
+ checkResp(res); ui.jsonOut(res.body); return;
35
+ }
36
+ case 'update': {
37
+ if (rest.length < 2) throw new Error('Usage: mayar customer update <fromEmail> <toEmail>');
38
+ const res = await api.request('POST', '/hl/v1/customer/update', {
39
+ apiKey, body: { fromEmail: rest[0], toEmail: rest[1] },
40
+ });
41
+ checkResp(res); ui.jsonOut(res.body); return;
42
+ }
43
+ case 'magic-link':
44
+ case 'magiclink': {
45
+ if (!rest[0]) throw new Error('Usage: mayar customer magic-link <email>');
46
+ const res = await api.request('POST', '/hl/v1/customer/login/portal', {
47
+ apiKey, body: { email: rest[0] },
48
+ });
49
+ checkResp(res); ui.jsonOut(res.body); return;
50
+ }
29
51
  default:
30
52
  throw new Error(USAGE);
31
53
  }
@@ -0,0 +1,42 @@
1
+ const api = require('../api');
2
+ const ui = require('../ui');
3
+ const { checkResp } = require('../util');
4
+
5
+ const USAGE = 'Usage: mayar review <list>';
6
+
7
+ function fmtDate(v) {
8
+ if (v == null) return '';
9
+ if (typeof v === 'number') return new Date(v).toISOString();
10
+ return String(v);
11
+ }
12
+
13
+ async function run({ apiKey, flags, positional }) {
14
+ const [sub] = positional;
15
+ switch (sub) {
16
+ case undefined:
17
+ case 'list': {
18
+ const res = await api.request('GET', '/hl/v1/reviews', {
19
+ apiKey, query: { page: flags.page, pageSize: flags.pageSize },
20
+ });
21
+ checkResp(res);
22
+ if (flags.json) return ui.jsonOut(res.body);
23
+ const data = (res.body && res.body.data) || [];
24
+ const rows = data.map((r) => ({
25
+ id: r.id,
26
+ rating: r.rating,
27
+ customer: (r.customer && (r.customer.name || r.customer.email)) || '',
28
+ message: r.message || '',
29
+ status: r.status || '',
30
+ createdAt: fmtDate(r.createdAt),
31
+ }));
32
+ ui.table(rows, ['id', 'rating', 'customer', 'message', 'status', 'createdAt']);
33
+ const m = res.body || {};
34
+ process.stdout.write(ui.dim(`page ${m.page ?? '?'} / ${m.pageCount ?? '?'} · total ${m.total ?? data.length}`) + '\n');
35
+ return;
36
+ }
37
+ default:
38
+ throw new Error(USAGE);
39
+ }
40
+ }
41
+
42
+ module.exports = { run };
@@ -2,7 +2,7 @@ const api = require('../api');
2
2
  const ui = require('../ui');
3
3
  const { checkResp } = require('../util');
4
4
 
5
- const USAGE = 'Usage: mayar tx <list|unpaid>';
5
+ const USAGE = 'Usage: mayar tx <list|unpaid|daily>';
6
6
 
7
7
  function fmtDate(v) {
8
8
  if (v == null) return '';
@@ -45,6 +45,17 @@ async function run({ apiKey, flags, positional }) {
45
45
  if (flags.json) return ui.jsonOut(res.body);
46
46
  renderTx(res.body); return;
47
47
  }
48
+ case 'daily': {
49
+ const res = await api.request('GET', '/hl/v1/transactions/daily', { apiKey });
50
+ checkResp(res);
51
+ if (flags.json) return ui.jsonOut(res.body);
52
+ const d = (res.body && res.body.data) || {};
53
+ if (d.date) process.stdout.write(`${ui.bold('Date:')} ${d.date}\n`);
54
+ if (d.tpvCount != null) process.stdout.write(`${ui.bold('Total volume:')} ${Number(d.tpvCount).toLocaleString('id-ID')}\n`);
55
+ if (d.trxCount != null) process.stdout.write(`${ui.bold('Transactions:')} ${d.trxCount}\n`);
56
+ if (!d.date && !d.tpvCount && !d.trxCount) ui.jsonOut(res.body);
57
+ return;
58
+ }
48
59
  default:
49
60
  throw new Error(USAGE);
50
61
  }