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 +8 -0
- package/dist/cli/accounts-cli.js +43 -0
- package/dist/cli/balance-cli.js +44 -0
- package/dist/cli/banks-cli.js +40 -0
- package/dist/cli/beneficiary-cli.js +65 -0
- package/dist/cli/bills-cli.js +107 -0
- package/dist/cli/confirm-cli.js +88 -0
- package/dist/cli/history-cli.js +64 -0
- package/dist/cli/kyc-cli.js +49 -0
- package/dist/cli/login-cli.js +126 -0
- package/dist/cli/logout-cli.js +42 -0
- package/dist/cli/profile-cli.js +40 -0
- package/dist/cli/resolve-cli.js +45 -0
- package/dist/cli/session-cli.js +101 -0
- package/dist/cli/settings-cli.js +65 -0
- package/dist/cli/transfer-cli.js +58 -0
- package/dist/index.js +42 -0
- package/dist/runtime.js +23 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/api.js +70 -0
- package/dist/utils/config.js +47 -0
- package/dist/utils/errors.js +26 -0
- package/package.json +31 -0
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
|
+
}
|
package/dist/runtime.js
ADDED
|
@@ -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,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
|
+
}
|