naira-agent 1.0.0

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 ADDED
@@ -0,0 +1,8 @@
1
+ # Naira Payment CLI
2
+
3
+ The Naira CLI provides a powerful financial layer for AI agents and developers. It supports transfers, bill payments, KYC, and session-based transactions.
4
+
5
+ ## 🚀 AI Agent Integration
6
+
7
+ We support **all** AI agents via the **Skill Protocol**. Whether you are using Claude Code, Antigravity, or Cursor, you can integrate Naira Payment as a skill.
8
+
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AccountsCommand = void 0;
7
+ exports.registerAccountsCli = registerAccountsCli;
8
+ const runtime_1 = require("../runtime");
9
+ const api_1 = __importDefault(require("../utils/api"));
10
+ const errors_1 = require("../utils/errors");
11
+ class AccountsCommand {
12
+ static register(program) {
13
+ const instance = new AccountsCommand();
14
+ const accounts = program
15
+ .command('accounts')
16
+ .description('Manage your virtual bank accounts. You can list existing accounts or create a new dedicated virtual account for receiving funds.');
17
+ accounts
18
+ .command('list')
19
+ .description('Retrieve a list of all your active virtual bank accounts, showing the bank name, account number, and account holder name.')
20
+ .action(() => instance.list());
21
+ }
22
+ async list() {
23
+ try {
24
+ const { data } = await api_1.default.get('/naira/accounts');
25
+ if (!data || data.length === 0) {
26
+ runtime_1.defaultRuntime.log('No virtual accounts found. They are usually created automatically after successful KYC.');
27
+ return;
28
+ }
29
+ runtime_1.defaultRuntime.log('--- Virtual Accounts ---');
30
+ data.forEach((acc) => {
31
+ runtime_1.defaultRuntime.log(`${acc.bankName.padEnd(20)} | ${acc.accountNumber} | ${acc.accountName}`);
32
+ });
33
+ runtime_1.defaultRuntime.log('------------------------');
34
+ }
35
+ catch (err) {
36
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to fetch accounts'));
37
+ }
38
+ }
39
+ }
40
+ exports.AccountsCommand = AccountsCommand;
41
+ function registerAccountsCli(program) {
42
+ AccountsCommand.register(program);
43
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BalanceCommand = void 0;
7
+ exports.registerBalanceCli = registerBalanceCli;
8
+ const runtime_1 = require("../runtime");
9
+ const api_1 = __importDefault(require("../utils/api"));
10
+ const errors_1 = require("../utils/errors");
11
+ class BalanceCommand {
12
+ static register(program) {
13
+ const instance = new BalanceCommand();
14
+ program
15
+ .command('balance')
16
+ .description('Retrieve the current available balance for your Jovita Naira account. The balance is returned in Naira (NGN).')
17
+ .action(() => instance.execute());
18
+ }
19
+ async execute() {
20
+ try {
21
+ const { data: balanceData } = await api_1.default.get('/naira/balance');
22
+ runtime_1.defaultRuntime.log(`Balance: ${balanceData.currency} ${balanceData.balance}`);
23
+ try {
24
+ const { data: accountData } = await api_1.default.get('/naira/accounts');
25
+ if (accountData && accountData.length > 0) {
26
+ runtime_1.defaultRuntime.log('\n--- Linked Accounts ---');
27
+ accountData.forEach((acc) => {
28
+ runtime_1.defaultRuntime.log(`${acc.bankName}: ${acc.accountNumber} (${acc.accountName})`);
29
+ });
30
+ }
31
+ }
32
+ catch (accountErr) {
33
+ runtime_1.defaultRuntime.warn('Could not fetch linked accounts.');
34
+ }
35
+ }
36
+ catch (err) {
37
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to fetch balance'));
38
+ }
39
+ }
40
+ }
41
+ exports.BalanceCommand = BalanceCommand;
42
+ function registerBalanceCli(program) {
43
+ BalanceCommand.register(program);
44
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BanksCommand = void 0;
7
+ exports.registerBanksCli = registerBanksCli;
8
+ const runtime_1 = require("../runtime");
9
+ const api_1 = __importDefault(require("../utils/api"));
10
+ const errors_1 = require("../utils/errors");
11
+ class BanksCommand {
12
+ static register(program) {
13
+ const instance = new BanksCommand();
14
+ program
15
+ .command('banks')
16
+ .description('Fetch and list all supported banks and their respective bank codes for transfers. Use these codes when initiating a transfer or resolving an account.')
17
+ .action(() => instance.execute());
18
+ }
19
+ async execute() {
20
+ try {
21
+ const { data } = await api_1.default.get('/naira/banks');
22
+ if (!data || data.length === 0) {
23
+ runtime_1.defaultRuntime.log('No banks found.');
24
+ return;
25
+ }
26
+ runtime_1.defaultRuntime.log('--- Supported Banks ---');
27
+ data.forEach((bank) => {
28
+ runtime_1.defaultRuntime.log(`${bank.name.padEnd(40)} | ${bank.code}`);
29
+ });
30
+ runtime_1.defaultRuntime.log('-----------------------');
31
+ }
32
+ catch (err) {
33
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Could not fetch banks'));
34
+ }
35
+ }
36
+ }
37
+ exports.BanksCommand = BanksCommand;
38
+ function registerBanksCli(program) {
39
+ BanksCommand.register(program);
40
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BeneficiaryCommand = void 0;
7
+ exports.registerBeneficiaryCli = registerBeneficiaryCli;
8
+ const runtime_1 = require("../runtime");
9
+ const api_1 = __importDefault(require("../utils/api"));
10
+ const errors_1 = require("../utils/errors");
11
+ class BeneficiaryCommand {
12
+ static register(program) {
13
+ const instance = new BeneficiaryCommand();
14
+ const beneficiary = program
15
+ .command('beneficiary')
16
+ .description('Manage your saved beneficiaries for quick transfers. You can list saved beneficiaries or add new ones to your account.');
17
+ beneficiary
18
+ .command('list')
19
+ .description('Fetch and display all your saved beneficiaries, showing their names, bank codes, and account numbers.')
20
+ .action(() => instance.list());
21
+ beneficiary
22
+ .command('add')
23
+ .description('Save a new beneficiary to your account for future transfers. Requires a bank code, account number, and the beneficiary name.')
24
+ .requiredOption('-n, --name <string>', 'The name to save for this beneficiary')
25
+ .requiredOption('-a, --account <number>', 'The account number of the beneficiary')
26
+ .requiredOption('-b, --bank <bankCode>', 'The bank code of the beneficiary bank')
27
+ .option('--bank-name <string>', 'Bank name')
28
+ .action((opts) => instance.add(opts));
29
+ }
30
+ async list() {
31
+ try {
32
+ const { data } = await api_1.default.get('/naira/beneficiaries');
33
+ if (!data || data.length === 0) {
34
+ runtime_1.defaultRuntime.log('No beneficiaries saved.');
35
+ return;
36
+ }
37
+ runtime_1.defaultRuntime.log('--- Beneficiaries ---');
38
+ data.forEach((b) => {
39
+ runtime_1.defaultRuntime.log(`${b.accountName.padEnd(20)} | ${b.bankName.padEnd(15)} | ${b.accountNumber}`);
40
+ });
41
+ runtime_1.defaultRuntime.log('---------------------');
42
+ }
43
+ catch (err) {
44
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Could not fetch beneficiaries'));
45
+ }
46
+ }
47
+ async add(opts) {
48
+ try {
49
+ const { data } = await api_1.default.post('/naira/beneficiaries', {
50
+ accountName: opts.name,
51
+ accountNumber: opts.account,
52
+ bankCode: opts.bank,
53
+ bankName: opts.bankName || 'Unknown Bank',
54
+ });
55
+ runtime_1.defaultRuntime.success(`Beneficiary ${data.accountName} added successfully.`);
56
+ }
57
+ catch (err) {
58
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Could not add beneficiary'));
59
+ }
60
+ }
61
+ }
62
+ exports.BeneficiaryCommand = BeneficiaryCommand;
63
+ function registerBeneficiaryCli(program) {
64
+ BeneficiaryCommand.register(program);
65
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.BillsCommand = void 0;
7
+ exports.registerBillsCli = registerBillsCli;
8
+ const runtime_1 = require("../runtime");
9
+ const api_1 = __importDefault(require("../utils/api"));
10
+ const errors_1 = require("../utils/errors");
11
+ class BillsCommand {
12
+ static register(program) {
13
+ const instance = new BillsCommand();
14
+ const bills = program
15
+ .command('bills')
16
+ .description('Pay for various bills including Airtime, Mobile Data, Cable TV, Electricity, and Betting. The command will guide you through selecting a biller and a service plan.');
17
+ bills
18
+ .command('categories')
19
+ .description('List bill categories')
20
+ .action(() => instance.listCategories());
21
+ bills
22
+ .command('billers')
23
+ .description('List billers for a category')
24
+ .requiredOption('-c, --category <type>', 'AIRTIME, DATA, TV, ELECTRICITY')
25
+ .action((opts) => instance.listBillers(opts.category));
26
+ bills
27
+ .command('plans')
28
+ .description('List plans for a biller')
29
+ .requiredOption('-c, --category <type>', 'AIRTIME, DATA, TV, ELECTRICITY')
30
+ .requiredOption('-b, --biller <id>', 'Biller ID')
31
+ .action((opts) => instance.listPlans(opts.category, opts.biller));
32
+ bills
33
+ .command('pay')
34
+ .description('Pay for a bill; include --session-token to skip OTP when a valid session exists')
35
+ .requiredOption('-c, --category <type>', 'AIRTIME, DATA, TV, ELECTRICITY')
36
+ .requiredOption('-b, --biller <id>', 'Biller ID')
37
+ .option('-p, --plan <id>', 'Plan/Variation ID (if required)')
38
+ .requiredOption('-t, --target <string>', 'Phone number, smartcard, or meter number')
39
+ .requiredOption('-a, --amount <number>', 'Amount')
40
+ .option('-s, --session-token <string>', 'Optional transaction session token')
41
+ .action((opts) => instance.executePay(opts));
42
+ }
43
+ listCategories() {
44
+ ['AIRTIME', 'DATA', 'TV', 'ELECTRICITY'].forEach(c => runtime_1.defaultRuntime.log(c));
45
+ }
46
+ async listBillers(category) {
47
+ try {
48
+ const { data } = await api_1.default.get('/naira/bills/billers', { params: { category } });
49
+ data.forEach((b) => runtime_1.defaultRuntime.log(`${b.id}: ${b.name}`));
50
+ }
51
+ catch (err) {
52
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to fetch billers'));
53
+ }
54
+ }
55
+ async listPlans(category, billerId) {
56
+ try {
57
+ const { data } = await api_1.default.get('/naira/bills/variations', { params: { category, billerId } });
58
+ data.forEach((v) => {
59
+ const id = v.id || v.variation_code;
60
+ const amount = v.amount ?? v.variation_amount;
61
+ const amountStr = (amount === '0' || amount === 0) ? 'Variable' : `N${amount}`;
62
+ runtime_1.defaultRuntime.log(`${id}: ${v.name} (${amountStr})`);
63
+ });
64
+ }
65
+ catch (err) {
66
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to fetch plans'));
67
+ }
68
+ }
69
+ async executePay(opts) {
70
+ try {
71
+ const amount = Number.parseFloat(opts.amount);
72
+ if (!Number.isFinite(amount) || amount <= 0) {
73
+ runtime_1.defaultRuntime.error('Amount must be a valid number greater than 0.');
74
+ return;
75
+ }
76
+ const { data } = await api_1.default.post('/naira/bills/pay', {
77
+ category: opts.category,
78
+ billerId: opts.biller,
79
+ variationId: opts.plan,
80
+ target: opts.target,
81
+ amount,
82
+ }, {
83
+ headers: opts.sessionToken
84
+ ? { 'X-Transaction-Session': opts.sessionToken }
85
+ : undefined,
86
+ });
87
+ if (data.status === 'pending_otp') {
88
+ runtime_1.defaultRuntime.log(`Payment pending. Email confirmation OTP sent to ${data.maskedEmail}`);
89
+ runtime_1.defaultRuntime.log(`Reference: ${data.reference}`);
90
+ runtime_1.defaultRuntime.log(`Confirm with: naira confirm -r ${data.reference} -c <code>`);
91
+ }
92
+ else {
93
+ runtime_1.defaultRuntime.success(`Payment successful!`);
94
+ runtime_1.defaultRuntime.log(`Reference: ${data.reference}`);
95
+ runtime_1.defaultRuntime.log(`Status: ${data.status}`);
96
+ runtime_1.defaultRuntime.log(`Amount: NGN ${data.amount}`);
97
+ }
98
+ }
99
+ catch (err) {
100
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Payment failed'));
101
+ }
102
+ }
103
+ }
104
+ exports.BillsCommand = BillsCommand;
105
+ function registerBillsCli(program) {
106
+ BillsCommand.register(program);
107
+ }
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ConfirmCommand = void 0;
7
+ exports.registerConfirmCli = registerConfirmCli;
8
+ const api_1 = __importDefault(require("../utils/api"));
9
+ const runtime_1 = require("../runtime");
10
+ const errors_1 = require("../utils/errors");
11
+ class ConfirmCommand {
12
+ static async execute(opts) {
13
+ try {
14
+ const reference = opts.reference;
15
+ const code = opts.code;
16
+ if (!reference) {
17
+ runtime_1.defaultRuntime.error('Reference is required (--reference or -r)');
18
+ return;
19
+ }
20
+ if (!code) {
21
+ runtime_1.defaultRuntime.error('OTP code is required (--code or -c)');
22
+ return;
23
+ }
24
+ // Determine endpoint based on reference prefix
25
+ let endpoint;
26
+ if (reference.startsWith('TX-')) {
27
+ endpoint = '/naira/transfer/confirm';
28
+ }
29
+ else if (reference.startsWith('BILL-')) {
30
+ endpoint = '/naira/bills/confirm';
31
+ }
32
+ else if (reference.startsWith('SES-')) {
33
+ endpoint = '/naira/session/confirm';
34
+ }
35
+ else {
36
+ runtime_1.defaultRuntime.error('Invalid reference format. Must start with TX-, BILL-, or SES-');
37
+ return;
38
+ }
39
+ const response = await api_1.default.post(endpoint, { reference, code });
40
+ if (response.data) {
41
+ const { sessionToken, sessionExpiresIn, transaction } = response.data;
42
+ if (sessionToken && sessionExpiresIn) {
43
+ runtime_1.defaultRuntime.log('Confirmation successful. Session token issued:');
44
+ if (response.data.sessionId) {
45
+ runtime_1.defaultRuntime.log(`Session ID: ${response.data.sessionId}`);
46
+ }
47
+ runtime_1.defaultRuntime.log(`Token: ${sessionToken}`);
48
+ runtime_1.defaultRuntime.log(`Expires in: ${Math.floor(sessionExpiresIn / 60)} minutes`);
49
+ }
50
+ else {
51
+ runtime_1.defaultRuntime.log('Transaction confirmed!');
52
+ }
53
+ if (transaction) {
54
+ runtime_1.defaultRuntime.log('--- Transaction Details ---');
55
+ runtime_1.defaultRuntime.log(`ID: ${transaction.id}`);
56
+ runtime_1.defaultRuntime.log(`Status: ${transaction.status}`);
57
+ runtime_1.defaultRuntime.log(`Reference: ${transaction.reference}`);
58
+ if (transaction.amount) {
59
+ runtime_1.defaultRuntime.log(`Amount: ${transaction.currency || 'NGN'} ${transaction.amount}`);
60
+ }
61
+ if (transaction.description) {
62
+ runtime_1.defaultRuntime.log(`Reason: ${transaction.description}`);
63
+ }
64
+ runtime_1.defaultRuntime.log('---------------------------');
65
+ }
66
+ else if (response.data) {
67
+ // Fallback for other non-transaction confirmations (like session) if transaction is not present
68
+ const { transaction: _, sessionToken: __, sessionId: ___, ...rest } = response.data;
69
+ if (Object.keys(rest).length > 0) {
70
+ runtime_1.defaultRuntime.log(JSON.stringify(rest, null, 2));
71
+ }
72
+ }
73
+ }
74
+ }
75
+ catch (err) {
76
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Confirmation failed'));
77
+ }
78
+ }
79
+ }
80
+ exports.ConfirmCommand = ConfirmCommand;
81
+ function registerConfirmCli(program) {
82
+ program
83
+ .command('confirm')
84
+ .description('Confirm a pending transfer, bill payment, or session request with an email OTP code')
85
+ .requiredOption('-r, --reference <string>', 'Reference (e.g. TX-ABC123, BILL-XYZ789, SES-XYZ123)')
86
+ .requiredOption('-c, --code <string>', 'Email confirmation OTP code')
87
+ .action((opts) => ConfirmCommand.execute(opts));
88
+ }
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HistoryCommand = void 0;
7
+ exports.registerHistoryCli = registerHistoryCli;
8
+ const runtime_1 = require("../runtime");
9
+ const api_1 = __importDefault(require("../utils/api"));
10
+ const errors_1 = require("../utils/errors");
11
+ class HistoryCommand {
12
+ static register(program) {
13
+ const instance = new HistoryCommand();
14
+ program
15
+ .command('history')
16
+ .description('View your recent transaction history, including transfers, bill payments, and deposits. Shows status, amount, and reference for each transaction.')
17
+ .option('-l, --limit <number>', 'Maximum number of transactions to display (default: 10)', '10')
18
+ .option('-o, --offset <number>', 'Offset for pagination', '0')
19
+ .action((opts) => instance.execute(opts));
20
+ }
21
+ async execute(opts) {
22
+ try {
23
+ const limit = Number.parseInt(opts.limit, 10);
24
+ const offset = Number.parseInt(opts.offset, 10);
25
+ if (!Number.isInteger(limit) || limit < 1) {
26
+ runtime_1.defaultRuntime.error('Limit must be an integer greater than 0.');
27
+ return;
28
+ }
29
+ if (!Number.isInteger(offset) || offset < 0) {
30
+ runtime_1.defaultRuntime.error('Offset must be an integer greater than or equal to 0.');
31
+ return;
32
+ }
33
+ const { data } = await api_1.default.get('/naira/history', {
34
+ params: {
35
+ limit,
36
+ offset,
37
+ }
38
+ });
39
+ if (!data || data.length === 0) {
40
+ runtime_1.defaultRuntime.log('No transactions found.');
41
+ return;
42
+ }
43
+ runtime_1.defaultRuntime.log('--- Transaction History ---');
44
+ data.forEach((tx) => {
45
+ const date = new Date(tx.createdAt).toLocaleString();
46
+ const amount = tx.amount.toLocaleString('en-NG', { style: 'currency', currency: tx.currency || 'NGN' });
47
+ const type = tx.type.padEnd(12);
48
+ const status = tx.status.padEnd(8);
49
+ runtime_1.defaultRuntime.log(`[${date}] ${type} | ${status} | ${amount} | Ref: ${tx.reference}`);
50
+ if (tx.description) {
51
+ runtime_1.defaultRuntime.log(` Description: ${tx.description}`);
52
+ }
53
+ });
54
+ runtime_1.defaultRuntime.log('---------------------------');
55
+ }
56
+ catch (err) {
57
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Could not fetch history'));
58
+ }
59
+ }
60
+ }
61
+ exports.HistoryCommand = HistoryCommand;
62
+ function registerHistoryCli(program) {
63
+ HistoryCommand.register(program);
64
+ }
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.KYCCommand = void 0;
7
+ exports.registerKYCCli = registerKYCCli;
8
+ const runtime_1 = require("../runtime");
9
+ const api_1 = __importDefault(require("../utils/api"));
10
+ const errors_1 = require("../utils/errors");
11
+ class KYCCommand {
12
+ static register(program) {
13
+ const instance = new KYCCommand();
14
+ program
15
+ .command('kyc')
16
+ .description('Perform KYC (Know Your Customer) verification to upgrade your account limits.')
17
+ .option('-t, --type <type>', 'Identity type (bvn, nin)')
18
+ .requiredOption('-n, --number <string>', 'ID number')
19
+ .requiredOption('-f, --firstname <string>', 'First name')
20
+ .requiredOption('-l, --lastname <string>', 'Last name')
21
+ .requiredOption('-d, --dob <string>', 'Date of birth (YYYY-MM-DD)')
22
+ .action((opts) => instance.execute(opts));
23
+ }
24
+ async execute(opts) {
25
+ try {
26
+ const { data } = await api_1.default.post('/naira/kyc/verify', {
27
+ type: opts.type,
28
+ idNumber: opts.number,
29
+ firstname: opts.firstname,
30
+ lastname: opts.lastname,
31
+ dob: opts.dob,
32
+ });
33
+ if (data.status === 'VERIFIED') {
34
+ runtime_1.defaultRuntime.success(`Verified successfully!`);
35
+ }
36
+ else {
37
+ runtime_1.defaultRuntime.warn(`Verification status: ${data.status}`);
38
+ }
39
+ runtime_1.defaultRuntime.log(`Message: ${data.message}`);
40
+ }
41
+ catch (err) {
42
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Verification failed'));
43
+ }
44
+ }
45
+ }
46
+ exports.KYCCommand = KYCCommand;
47
+ function registerKYCCli(program) {
48
+ KYCCommand.register(program);
49
+ }
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.LoginCommand = void 0;
40
+ exports.registerLoginCli = registerLoginCli;
41
+ const axios_1 = __importDefault(require("axios"));
42
+ const crypto = __importStar(require("crypto"));
43
+ const runtime_1 = require("../runtime");
44
+ const api_1 = __importDefault(require("../utils/api"));
45
+ const config_1 = __importDefault(require("../utils/config"));
46
+ const errors_1 = require("../utils/errors");
47
+ const MAX_POLL_DURATION_MS = 5 * 60 * 1000; // 5 minutes
48
+ const MAX_POLL_ATTEMPTS = 60;
49
+ class LoginCommand {
50
+ static register(program) {
51
+ const instance = new LoginCommand();
52
+ program
53
+ .command('login')
54
+ .description('Authenticate your CLI session with the Jovita Payment Server using the Device Flow. This will provide a link and a user code for you to approve in your browser.')
55
+ .action(() => instance.execute());
56
+ }
57
+ async execute() {
58
+ try {
59
+ const agentName = process.env.NAIRA_AGENT_NAME?.trim() ||
60
+ process.env.AGENT_NAME?.trim() ||
61
+ 'Naira CLI';
62
+ let deviceId = config_1.default.get('deviceId');
63
+ if (!deviceId) {
64
+ deviceId = crypto.randomUUID();
65
+ config_1.default.set('deviceId', deviceId);
66
+ }
67
+ const { data } = await api_1.default.post('/auth/device/authorize', {
68
+ client_id: deviceId,
69
+ client_name: agentName,
70
+ });
71
+ runtime_1.defaultRuntime.log(`Visit: ${data.verification_uri_complete || data.verification_uri}`);
72
+ if (!data.verification_uri_complete) {
73
+ runtime_1.defaultRuntime.log(`Enter Code: ${data.user_code}`);
74
+ }
75
+ runtime_1.defaultRuntime.log(`Waiting for approval...`);
76
+ const pollInterval = data.interval * 1000 || 5000;
77
+ const device_code = data.device_code;
78
+ const client_id = deviceId;
79
+ const client_name = agentName;
80
+ const startTime = Date.now();
81
+ let attempts = 0;
82
+ const poll = setInterval(async () => {
83
+ attempts++;
84
+ if (attempts > MAX_POLL_ATTEMPTS || (Date.now() - startTime) > MAX_POLL_DURATION_MS) {
85
+ clearInterval(poll);
86
+ runtime_1.defaultRuntime.error('Login timed out. Please try again.');
87
+ process.exit(1);
88
+ }
89
+ try {
90
+ const tokenRes = await api_1.default.post('/auth/device/token', {
91
+ device_code,
92
+ client_id,
93
+ client_name,
94
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
95
+ });
96
+ if (tokenRes.data.access_token) {
97
+ clearInterval(poll);
98
+ config_1.default.set('accessToken', tokenRes.data.access_token);
99
+ config_1.default.set('refreshToken', tokenRes.data.refresh_token);
100
+ runtime_1.defaultRuntime.success('Successfully authenticated!');
101
+ process.exit(0);
102
+ }
103
+ }
104
+ catch (err) {
105
+ if (axios_1.default.isAxiosError(err)) {
106
+ const errorRes = err.response?.data;
107
+ const errorMessage = errorRes?.message || errorRes?.error;
108
+ if (errorMessage === 'authorization_pending') {
109
+ return;
110
+ }
111
+ clearInterval(poll);
112
+ runtime_1.defaultRuntime.error(errorMessage || 'Login failed');
113
+ process.exit(1);
114
+ }
115
+ }
116
+ }, pollInterval);
117
+ }
118
+ catch (err) {
119
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to initiate login'));
120
+ }
121
+ }
122
+ }
123
+ exports.LoginCommand = LoginCommand;
124
+ function registerLoginCli(program) {
125
+ LoginCommand.register(program);
126
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.LogoutCommand = void 0;
7
+ exports.registerLogoutCli = registerLogoutCli;
8
+ const runtime_1 = require("../runtime");
9
+ const api_1 = __importDefault(require("../utils/api"));
10
+ const config_1 = __importDefault(require("../utils/config"));
11
+ const errors_1 = require("../utils/errors");
12
+ class LogoutCommand {
13
+ static register(program) {
14
+ const instance = new LogoutCommand();
15
+ program
16
+ .command('logout')
17
+ .description('Terminate your current Jovita CLI session and securely remove your authentication token from the local machine.')
18
+ .action(() => instance.execute());
19
+ }
20
+ async execute() {
21
+ try {
22
+ const refreshToken = config_1.default.get('refreshToken');
23
+ await api_1.default.post('/auth/logout', {
24
+ refresh_token: refreshToken
25
+ }).catch(() => {
26
+ runtime_1.defaultRuntime.warn('Could not notify server. Token may still be valid server-side.');
27
+ });
28
+ config_1.default.delete('accessToken');
29
+ config_1.default.delete('refreshToken');
30
+ config_1.default.delete('user');
31
+ config_1.default.delete('deviceId');
32
+ runtime_1.defaultRuntime.success('Logged out successfully from this device.');
33
+ }
34
+ catch (err) {
35
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'An unexpected error occurred during logout.'));
36
+ }
37
+ }
38
+ }
39
+ exports.LogoutCommand = LogoutCommand;
40
+ function registerLogoutCli(program) {
41
+ LogoutCommand.register(program);
42
+ }
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ProfileCommand = void 0;
7
+ exports.registerProfileCli = registerProfileCli;
8
+ const runtime_1 = require("../runtime");
9
+ const api_1 = __importDefault(require("../utils/api"));
10
+ const errors_1 = require("../utils/errors");
11
+ class ProfileCommand {
12
+ static register(program) {
13
+ const instance = new ProfileCommand();
14
+ program
15
+ .command('profile')
16
+ .description('Display your user profile information, including name, email, and current KYC (Know Your Customer) verification status.')
17
+ .action(() => instance.execute());
18
+ }
19
+ async execute() {
20
+ try {
21
+ const { data } = await api_1.default.get('/naira/profile');
22
+ const { profile, balance } = data;
23
+ runtime_1.defaultRuntime.log('--- User Profile ---');
24
+ runtime_1.defaultRuntime.log(`Name: ${profile.firstname} ${profile.lastname}`);
25
+ runtime_1.defaultRuntime.log(`Email: ${profile.email}`);
26
+ runtime_1.defaultRuntime.log(`Phone: ${profile.phone || 'N/A'}`);
27
+ runtime_1.defaultRuntime.log(`Status: ${profile.status}`);
28
+ runtime_1.defaultRuntime.log(`KYC: Tier ${profile.kycTier} (${profile.kycVerificationStatus})`);
29
+ runtime_1.defaultRuntime.log(`Balance: NGN ${balance.toLocaleString()}`);
30
+ runtime_1.defaultRuntime.log('--------------------');
31
+ }
32
+ catch (err) {
33
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to fetch profile'));
34
+ }
35
+ }
36
+ }
37
+ exports.ProfileCommand = ProfileCommand;
38
+ function registerProfileCli(program) {
39
+ ProfileCommand.register(program);
40
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ResolveCommand = void 0;
7
+ exports.registerResolveCli = registerResolveCli;
8
+ const runtime_1 = require("../runtime");
9
+ const api_1 = __importDefault(require("../utils/api"));
10
+ const errors_1 = require("../utils/errors");
11
+ class ResolveCommand {
12
+ static register(program) {
13
+ const instance = new ResolveCommand();
14
+ program
15
+ .command('resolve')
16
+ .description('Resolve a bank account number to an account name. This is useful for verifying recipient details before initiating a transfer.')
17
+ .requiredOption('-b, --bank <bankCode>', 'The bank code of the recipient bank (e.g., 100004 for OPay)')
18
+ .requiredOption('-a, --account <number>', 'The account number to resolve')
19
+ .action((opts) => instance.execute(opts));
20
+ }
21
+ async execute(opts) {
22
+ try {
23
+ const { data } = await api_1.default.post('/naira/transfer/resolve', {
24
+ bankCode: opts.bank,
25
+ accountNumber: opts.account,
26
+ });
27
+ if (!data || !data.accountName) {
28
+ runtime_1.defaultRuntime.error('Could not resolve account name. Please verify the bank code and account number.');
29
+ return;
30
+ }
31
+ runtime_1.defaultRuntime.log('--- Account Details ---');
32
+ runtime_1.defaultRuntime.log(`Account Name: ${data.accountName}`);
33
+ runtime_1.defaultRuntime.log(`Account Number: ${opts.account}`);
34
+ runtime_1.defaultRuntime.log(`Bank Code: ${opts.bank}`);
35
+ runtime_1.defaultRuntime.log('-----------------------');
36
+ }
37
+ catch (err) {
38
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Account resolution failed'));
39
+ }
40
+ }
41
+ }
42
+ exports.ResolveCommand = ResolveCommand;
43
+ function registerResolveCli(program) {
44
+ ResolveCommand.register(program);
45
+ }
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SessionCommand = void 0;
7
+ exports.registerSessionCli = registerSessionCli;
8
+ const api_1 = __importDefault(require("../utils/api"));
9
+ const runtime_1 = require("../runtime");
10
+ const errors_1 = require("../utils/errors");
11
+ class SessionCommand {
12
+ static async request(opts) {
13
+ try {
14
+ const durationMinutes = opts.duration ? Number.parseInt(opts.duration, 10) : undefined;
15
+ const response = await api_1.default.post('/naira/session/request', { label: opts.label, durationMinutes });
16
+ const data = response.data;
17
+ runtime_1.defaultRuntime.log(data.message || 'Session request submitted.');
18
+ if (data.sessionToken && data.sessionExpiresIn) {
19
+ runtime_1.defaultRuntime.log(`Session issued immediately: ${data.sessionId}`);
20
+ runtime_1.defaultRuntime.log(`Token: ${data.sessionToken}`);
21
+ if (data.sessionExpiresIn) {
22
+ runtime_1.defaultRuntime.log(`Expires in: ${Math.floor(data.sessionExpiresIn / 60)} minutes`);
23
+ }
24
+ return;
25
+ }
26
+ if (data.reference && data.reference.startsWith('SES-')) {
27
+ runtime_1.defaultRuntime.log(`Reference: ${data.reference}`);
28
+ runtime_1.defaultRuntime.log(`Confirm with: naira-agent confirm -r ${data.reference} -c <code>`);
29
+ }
30
+ }
31
+ catch (err) {
32
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to request session'));
33
+ }
34
+ }
35
+ static async getStatus() {
36
+ try {
37
+ const response = await api_1.default.get('/naira/session/status');
38
+ const data = response.data;
39
+ if (!data.active) {
40
+ runtime_1.defaultRuntime.log('No active transaction sessions.');
41
+ return;
42
+ }
43
+ runtime_1.defaultRuntime.log(`Active transaction sessions: ${data.count ?? 1}`);
44
+ }
45
+ catch (err) {
46
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to get session status'));
47
+ }
48
+ }
49
+ static async revoke(opts) {
50
+ try {
51
+ await api_1.default.post('/naira/session/revoke', { sessionId: opts?.sessionId });
52
+ runtime_1.defaultRuntime.log(opts?.sessionId ? `Session ${opts.sessionId} revoked.` : 'All transaction sessions revoked.');
53
+ }
54
+ catch (err) {
55
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to revoke session'));
56
+ }
57
+ }
58
+ static async listSessions() {
59
+ try {
60
+ const response = await api_1.default.get('/naira/session');
61
+ const sessions = response.data;
62
+ if (!Array.isArray(sessions) || sessions.length === 0) {
63
+ runtime_1.defaultRuntime.log('No active transaction sessions.');
64
+ return;
65
+ }
66
+ runtime_1.defaultRuntime.log('Active transaction sessions:');
67
+ sessions.forEach((s) => {
68
+ runtime_1.defaultRuntime.log(`- [${s.sessionId}] ${s.label || 'unlabeled'} | ${s.durationMinutes ?? '?'} mins | expires ${new Date(s.expiresAt).toLocaleString()}`);
69
+ runtime_1.defaultRuntime.log(` token: ${s.token}`);
70
+ });
71
+ }
72
+ catch (err) {
73
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to list sessions'));
74
+ }
75
+ }
76
+ }
77
+ exports.SessionCommand = SessionCommand;
78
+ function registerSessionCli(program) {
79
+ const session = program
80
+ .command('session')
81
+ .description('Manage transaction sessions (request, list, status, revoke)');
82
+ session
83
+ .command('request')
84
+ .description('Request a transaction session for task execution (OTP confirmation required when OTP is enabled)')
85
+ .option('-l, --label <string>', 'Optional label (e.g. bulk payouts, nightly bot)')
86
+ .option('-d, --duration <minutes>', 'Session duration in minutes (5-120, default 30)')
87
+ .action((opts) => SessionCommand.request(opts));
88
+ session
89
+ .command('status')
90
+ .description('Check whether any transaction sessions are currently active')
91
+ .action(() => SessionCommand.getStatus());
92
+ session
93
+ .command('list')
94
+ .description('List active transaction sessions with labels, expiry, and token values')
95
+ .action(() => SessionCommand.listSessions());
96
+ session
97
+ .command('revoke')
98
+ .description('Revoke transaction sessions (all by default, or one via --session-id)')
99
+ .option('-i, --session-id <string>', 'Revoke a specific session by ID')
100
+ .action((opts) => SessionCommand.revoke(opts));
101
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SettingsCommand = void 0;
7
+ exports.registerSettingsCli = registerSettingsCli;
8
+ const api_1 = __importDefault(require("../utils/api"));
9
+ const runtime_1 = require("../runtime");
10
+ const errors_1 = require("../utils/errors");
11
+ class SettingsCommand {
12
+ static register(program) {
13
+ const settings = program
14
+ .command('settings')
15
+ .description('Manage account settings');
16
+ const otp = settings
17
+ .command('otp')
18
+ .description('Email confirmation OTP settings for transactions');
19
+ otp
20
+ .command('enable')
21
+ .description('Enable email confirmation OTP for transactions')
22
+ .action(() => SettingsCommand.enableOtp());
23
+ otp
24
+ .command('disable')
25
+ .description('Disable email confirmation OTP for transactions')
26
+ .action(() => SettingsCommand.disableOtp());
27
+ otp
28
+ .command('status')
29
+ .description('Check email confirmation OTP status')
30
+ .action(() => SettingsCommand.otpStatus());
31
+ }
32
+ static async enableOtp() {
33
+ try {
34
+ const { data } = await api_1.default.post('/naira/settings/otp/enable');
35
+ runtime_1.defaultRuntime.success('Email confirmation OTP enabled for transactions.');
36
+ runtime_1.defaultRuntime.log(`Status: ${data.enabled ? 'enabled' : 'disabled'}`);
37
+ }
38
+ catch (err) {
39
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to enable OTP'));
40
+ }
41
+ }
42
+ static async disableOtp() {
43
+ try {
44
+ const { data } = await api_1.default.post('/naira/settings/otp/disable');
45
+ runtime_1.defaultRuntime.log('Email confirmation OTP disabled. Transactions will execute immediately.');
46
+ runtime_1.defaultRuntime.log(`Status: ${data.enabled ? 'enabled' : 'disabled'}`);
47
+ }
48
+ catch (err) {
49
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to disable OTP'));
50
+ }
51
+ }
52
+ static async otpStatus() {
53
+ try {
54
+ const { data } = await api_1.default.get('/naira/settings/otp/status');
55
+ runtime_1.defaultRuntime.log(`Email confirmation OTP: ${data.enabled ? 'enabled' : 'disabled'}`);
56
+ }
57
+ catch (err) {
58
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Failed to get OTP status'));
59
+ }
60
+ }
61
+ }
62
+ exports.SettingsCommand = SettingsCommand;
63
+ function registerSettingsCli(program) {
64
+ SettingsCommand.register(program);
65
+ }
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TransferCommand = void 0;
7
+ exports.registerTransferCli = registerTransferCli;
8
+ const api_1 = __importDefault(require("../utils/api"));
9
+ const runtime_1 = require("../runtime");
10
+ const errors_1 = require("../utils/errors");
11
+ class TransferCommand {
12
+ static async execute(opts) {
13
+ try {
14
+ const amount = Number.parseFloat(opts.amount);
15
+ if (!Number.isFinite(amount) || amount <= 0) {
16
+ runtime_1.defaultRuntime.error('Amount must be a valid number greater than 0.');
17
+ return;
18
+ }
19
+ const response = await api_1.default.post('/naira/transfer', {
20
+ bankCode: opts.bankCode,
21
+ accountNumber: opts.accountNumber,
22
+ amount,
23
+ reason: opts.reason || 'Transfer',
24
+ }, {
25
+ headers: opts.sessionToken
26
+ ? { 'X-Transaction-Session': opts.sessionToken }
27
+ : undefined,
28
+ });
29
+ const data = response.data;
30
+ if (data.status === 'pending_otp') {
31
+ runtime_1.defaultRuntime.log(`Transaction pending. Email confirmation OTP sent to ${data.maskedEmail}`);
32
+ runtime_1.defaultRuntime.log(`Reference: ${data.reference}`);
33
+ runtime_1.defaultRuntime.log(`Confirm with: naira confirm -r ${data.reference} -c <code>`);
34
+ }
35
+ else {
36
+ runtime_1.defaultRuntime.success('Transfer initiated successfully!');
37
+ runtime_1.defaultRuntime.log(`Reference: ${data.reference}`);
38
+ runtime_1.defaultRuntime.log(`Status: ${data.status}`);
39
+ runtime_1.defaultRuntime.log(`Amount: NGN ${data.amount}`);
40
+ }
41
+ }
42
+ catch (err) {
43
+ runtime_1.defaultRuntime.error((0, errors_1.sanitizeErrorMessage)(err, 'Transfer failed'));
44
+ }
45
+ }
46
+ }
47
+ exports.TransferCommand = TransferCommand;
48
+ function registerTransferCli(program) {
49
+ program
50
+ .command('transfer')
51
+ .description('Initiate a bank transfer; include --session-token to skip OTP when a valid session exists')
52
+ .requiredOption('-b, --bank-code <string>', 'Destination bank code')
53
+ .requiredOption('-a, --account-number <string>', 'Destination account number')
54
+ .requiredOption('-m, --amount <number>', 'Amount in Naira')
55
+ .option('-r, --reason <string>', 'Transfer reason/narration')
56
+ .option('-s, --session-token <string>', 'Optional transaction session token')
57
+ .action((opts) => TransferCommand.execute(opts));
58
+ }
package/dist/index.js ADDED
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const commander_1 = require("commander");
5
+ const login_cli_1 = require("./cli/login-cli");
6
+ const balance_cli_1 = require("./cli/balance-cli");
7
+ const transfer_cli_1 = require("./cli/transfer-cli");
8
+ const bills_cli_1 = require("./cli/bills-cli");
9
+ const confirm_cli_1 = require("./cli/confirm-cli");
10
+ const history_cli_1 = require("./cli/history-cli");
11
+ const beneficiary_cli_1 = require("./cli/beneficiary-cli");
12
+ const banks_cli_1 = require("./cli/banks-cli");
13
+ const accounts_cli_1 = require("./cli/accounts-cli");
14
+ const profile_cli_1 = require("./cli/profile-cli");
15
+ const settings_cli_1 = require("./cli/settings-cli");
16
+ const logout_cli_1 = require("./cli/logout-cli");
17
+ const resolve_cli_1 = require("./cli/resolve-cli");
18
+ const session_cli_1 = require("./cli/session-cli");
19
+ const { version } = require('../package.json');
20
+ const program = new commander_1.Command();
21
+ program
22
+ .name('naira-agent')
23
+ .description('CLI for Naira Payments: transfers, bill payments, KYC, account operations, and OTP-gated transaction sessions for AI or long-running tasks.')
24
+ .version(version);
25
+ (0, login_cli_1.registerLoginCli)(program);
26
+ (0, balance_cli_1.registerBalanceCli)(program);
27
+ (0, transfer_cli_1.registerTransferCli)(program);
28
+ (0, bills_cli_1.registerBillsCli)(program);
29
+ (0, confirm_cli_1.registerConfirmCli)(program);
30
+ (0, history_cli_1.registerHistoryCli)(program);
31
+ (0, beneficiary_cli_1.registerBeneficiaryCli)(program);
32
+ (0, banks_cli_1.registerBanksCli)(program);
33
+ (0, accounts_cli_1.registerAccountsCli)(program);
34
+ (0, profile_cli_1.registerProfileCli)(program);
35
+ (0, settings_cli_1.registerSettingsCli)(program);
36
+ (0, logout_cli_1.registerLogoutCli)(program);
37
+ (0, resolve_cli_1.registerResolveCli)(program);
38
+ (0, session_cli_1.registerSessionCli)(program);
39
+ program.parse(process.argv);
40
+ if (!process.argv.slice(2).length) {
41
+ program.outputHelp();
42
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.defaultRuntime = exports.Runtime = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ class Runtime {
9
+ log(message) {
10
+ console.log(message);
11
+ }
12
+ error(message) {
13
+ console.error(chalk_1.default.red(`Error: ${message}`));
14
+ }
15
+ success(message) {
16
+ console.log(chalk_1.default.green(message));
17
+ }
18
+ warn(message) {
19
+ console.log(chalk_1.default.yellow(`Warning: ${message}`));
20
+ }
21
+ }
22
+ exports.Runtime = Runtime;
23
+ exports.defaultRuntime = new Runtime();
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const axios_1 = __importDefault(require("axios"));
7
+ const config_1 = __importDefault(require("./config"));
8
+ const api = axios_1.default.create({
9
+ baseURL: config_1.default.get('serverUrl'),
10
+ timeout: 30_000,
11
+ });
12
+ const retriedRequests = new WeakSet();
13
+ api.interceptors.request.use((req) => {
14
+ const token = config_1.default.get('accessToken');
15
+ if (token) {
16
+ req.headers.Authorization = `Bearer ${token}`;
17
+ }
18
+ return req;
19
+ });
20
+ api.interceptors.response.use((response) => {
21
+ if (response.data && typeof response.data === 'object' && 'data' in response.data) {
22
+ return { ...response, data: response.data.data };
23
+ }
24
+ return response;
25
+ }, async (error) => {
26
+ const originalRequest = error.config;
27
+ if (!originalRequest) {
28
+ return Promise.reject(error);
29
+ }
30
+ if (error.response?.status === 401 && !retriedRequests.has(originalRequest)) {
31
+ retriedRequests.add(originalRequest);
32
+ const refreshToken = config_1.default.get('refreshToken');
33
+ if (refreshToken) {
34
+ try {
35
+ const { data } = await axios_1.default.post(`${config_1.default.get('serverUrl')}/auth/refresh`, {
36
+ refresh_token: refreshToken,
37
+ });
38
+ const payload = data?.data ?? data;
39
+ const nextAccessToken = payload?.access_token;
40
+ const nextRefreshToken = payload?.refresh_token;
41
+ if (typeof nextAccessToken === 'string' && nextAccessToken.length > 0) {
42
+ config_1.default.set('accessToken', nextAccessToken);
43
+ }
44
+ else {
45
+ config_1.default.delete('accessToken');
46
+ }
47
+ if (typeof nextRefreshToken === 'string' && nextRefreshToken.length > 0) {
48
+ config_1.default.set('refreshToken', nextRefreshToken);
49
+ }
50
+ else {
51
+ config_1.default.delete('refreshToken');
52
+ }
53
+ if (!nextAccessToken) {
54
+ return Promise.reject(new Error('Token refresh did not return an access token'));
55
+ }
56
+ originalRequest.headers = originalRequest.headers || {};
57
+ originalRequest.headers.Authorization = `Bearer ${nextAccessToken}`;
58
+ return api(originalRequest);
59
+ }
60
+ catch (refreshError) {
61
+ config_1.default.delete('accessToken');
62
+ config_1.default.delete('refreshToken');
63
+ config_1.default.delete('user');
64
+ return Promise.reject(refreshError);
65
+ }
66
+ }
67
+ }
68
+ return Promise.reject(error);
69
+ });
70
+ exports.default = api;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const conf_1 = __importDefault(require("conf"));
10
+ const schema = {
11
+ serverUrl: {
12
+ type: 'string',
13
+ default: 'https://api-naira.usebuildera.com/api',
14
+ },
15
+ accessToken: {
16
+ type: 'string',
17
+ },
18
+ refreshToken: {
19
+ type: 'string',
20
+ },
21
+ deviceId: {
22
+ type: 'string',
23
+ },
24
+ user: {
25
+ type: 'object',
26
+ properties: {
27
+ email: { type: 'string' },
28
+ firstname: { type: 'string' },
29
+ lastname: { type: 'string' },
30
+ }
31
+ }
32
+ };
33
+ const config = new conf_1.default({
34
+ schema,
35
+ projectName: 'naira-agent',
36
+ cwd: path_1.default.join(os_1.default.homedir(), '.naira-agent'),
37
+ configName: 'config'
38
+ });
39
+ // Restrict config file to owner-only read/write (0600)
40
+ // Prevents other users/processes from reading stored tokens
41
+ try {
42
+ fs_1.default.chmodSync(config.path, 0o600);
43
+ }
44
+ catch {
45
+ // Ignore if file doesn't exist yet — will be created on first write
46
+ }
47
+ exports.default = config;
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sanitizeErrorMessage = sanitizeErrorMessage;
7
+ const axios_1 = __importDefault(require("axios"));
8
+ const MAX_ERROR_LENGTH = 200;
9
+ /**
10
+ * Extract a safe, user-facing error message from an unknown error.
11
+ * Strips stack traces and truncates excessively long messages.
12
+ */
13
+ function sanitizeErrorMessage(err, fallback) {
14
+ if (axios_1.default.isAxiosError(err)) {
15
+ const serverMessage = err.response?.data?.message;
16
+ if (typeof serverMessage === 'string' && serverMessage.length > 0) {
17
+ return serverMessage.slice(0, MAX_ERROR_LENGTH);
18
+ }
19
+ return fallback;
20
+ }
21
+ if (err instanceof Error) {
22
+ // Never expose raw stack traces
23
+ return err.message.slice(0, MAX_ERROR_LENGTH);
24
+ }
25
+ return fallback;
26
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "naira-agent",
3
+ "version": "1.0.0",
4
+ "description": "Terminal client for NairaAgent system",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "naira-agent": "dist/index.js"
8
+ },
9
+ "files": [
10
+ "dist"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "test": "vitest run --config vitest.config.ts",
15
+ "lint": "echo \"Error: no lint specified\" && exit 0",
16
+ "start": "node dist/index.js",
17
+ "dev": "ts-node src/index.ts"
18
+ },
19
+ "dependencies": {
20
+ "axios": "^1.6.0",
21
+ "chalk": "^4.1.2",
22
+ "commander": "^11.1.0",
23
+ "conf": "^11.0.2"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^20.8.0",
27
+ "ts-node": "^10.9.1",
28
+ "typescript": "^5.2.2",
29
+ "vitest": "^2.1.8"
30
+ }
31
+ }