@ktmcp-cli/nordigen 1.0.0 → 1.0.2
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/AGENT.md +153 -434
- package/README.md +133 -321
- package/bin/nordigen.js +2 -76
- package/package.json +21 -21
- package/src/api.js +229 -0
- package/src/config.js +59 -0
- package/src/index.js +633 -0
- package/.env.example +0 -11
- package/.eslintrc.json +0 -17
- package/CHANGELOG.md +0 -69
- package/CONTRIBUTING.md +0 -198
- package/EXAMPLES.md +0 -561
- package/INDEX.md +0 -193
- package/OPENCLAW.md +0 -468
- package/PROJECT.md +0 -366
- package/QUICKREF.md +0 -231
- package/SETUP.md +0 -259
- package/SUMMARY.md +0 -419
- package/banner.png +0 -0
- package/logo.png +0 -0
- package/scripts/quickstart.sh +0 -110
- package/src/commands/accounts.js +0 -205
- package/src/commands/agreements.js +0 -241
- package/src/commands/auth.js +0 -86
- package/src/commands/config.js +0 -173
- package/src/commands/institutions.js +0 -181
- package/src/commands/payments.js +0 -228
- package/src/commands/requisitions.js +0 -239
- package/src/lib/api.js +0 -491
- package/src/lib/auth.js +0 -113
- package/src/lib/config.js +0 -145
- package/src/lib/output.js +0 -255
- package/test/api.test.js +0 -88
package/src/index.js
ADDED
|
@@ -0,0 +1,633 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getConfig, setConfig, isConfigured } from './config.js';
|
|
5
|
+
import {
|
|
6
|
+
listInstitutions,
|
|
7
|
+
getInstitution,
|
|
8
|
+
listAgreements,
|
|
9
|
+
getAgreement,
|
|
10
|
+
createAgreement,
|
|
11
|
+
deleteAgreement,
|
|
12
|
+
acceptAgreement,
|
|
13
|
+
listRequisitions,
|
|
14
|
+
getRequisition,
|
|
15
|
+
createRequisition,
|
|
16
|
+
deleteRequisition,
|
|
17
|
+
getAccountMetadata,
|
|
18
|
+
getAccountBalances,
|
|
19
|
+
getAccountDetails,
|
|
20
|
+
getAccountTransactions
|
|
21
|
+
} from './api.js';
|
|
22
|
+
|
|
23
|
+
const program = new Command();
|
|
24
|
+
|
|
25
|
+
// ============================================================
|
|
26
|
+
// Helpers
|
|
27
|
+
// ============================================================
|
|
28
|
+
|
|
29
|
+
function printSuccess(message) {
|
|
30
|
+
console.log(chalk.green('✓') + ' ' + message);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function printError(message) {
|
|
34
|
+
console.error(chalk.red('✗') + ' ' + message);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function printTable(data, columns) {
|
|
38
|
+
if (!data || data.length === 0) {
|
|
39
|
+
console.log(chalk.yellow('No results found.'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const widths = {};
|
|
44
|
+
columns.forEach(col => {
|
|
45
|
+
widths[col.key] = col.label.length;
|
|
46
|
+
data.forEach(row => {
|
|
47
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
48
|
+
if (val.length > widths[col.key]) widths[col.key] = val.length;
|
|
49
|
+
});
|
|
50
|
+
widths[col.key] = Math.min(widths[col.key], 50);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
|
|
54
|
+
console.log(chalk.bold(chalk.cyan(header)));
|
|
55
|
+
console.log(chalk.dim('─'.repeat(header.length)));
|
|
56
|
+
|
|
57
|
+
data.forEach(row => {
|
|
58
|
+
const line = columns.map(col => {
|
|
59
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
60
|
+
return val.substring(0, widths[col.key]).padEnd(widths[col.key]);
|
|
61
|
+
}).join(' ');
|
|
62
|
+
console.log(line);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
console.log(chalk.dim(`\n${data.length} result(s)`));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function printJson(data) {
|
|
69
|
+
console.log(JSON.stringify(data, null, 2));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function withSpinner(message, fn) {
|
|
73
|
+
const spinner = ora(message).start();
|
|
74
|
+
try {
|
|
75
|
+
const result = await fn();
|
|
76
|
+
spinner.stop();
|
|
77
|
+
return result;
|
|
78
|
+
} catch (error) {
|
|
79
|
+
spinner.stop();
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function requireAuth() {
|
|
85
|
+
if (!isConfigured()) {
|
|
86
|
+
printError('Nordigen credentials not configured.');
|
|
87
|
+
console.log('\nRun the following to configure:');
|
|
88
|
+
console.log(chalk.cyan(' nordigencom config set --secret-id <id> --secret-key <key>'));
|
|
89
|
+
console.log(chalk.cyan(' nordigencom auth login'));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ============================================================
|
|
95
|
+
// Program metadata
|
|
96
|
+
// ============================================================
|
|
97
|
+
|
|
98
|
+
program
|
|
99
|
+
.name('nordigencom')
|
|
100
|
+
.description(chalk.bold('Nordigen CLI') + ' - Account Information Services from your terminal')
|
|
101
|
+
.version('1.0.0');
|
|
102
|
+
|
|
103
|
+
// ============================================================
|
|
104
|
+
// CONFIG
|
|
105
|
+
// ============================================================
|
|
106
|
+
|
|
107
|
+
const configCmd = program.command('config').description('Manage CLI configuration');
|
|
108
|
+
|
|
109
|
+
configCmd
|
|
110
|
+
.command('set')
|
|
111
|
+
.description('Set configuration values')
|
|
112
|
+
.option('--secret-id <id>', 'Nordigen Secret ID')
|
|
113
|
+
.option('--secret-key <key>', 'Nordigen Secret Key')
|
|
114
|
+
.action((options) => {
|
|
115
|
+
if (options.secretId) {
|
|
116
|
+
setConfig('secretId', options.secretId);
|
|
117
|
+
printSuccess(`Secret ID set`);
|
|
118
|
+
}
|
|
119
|
+
if (options.secretKey) {
|
|
120
|
+
setConfig('secretKey', options.secretKey);
|
|
121
|
+
printSuccess(`Secret Key set`);
|
|
122
|
+
}
|
|
123
|
+
if (!options.secretId && !options.secretKey) {
|
|
124
|
+
printError('No options provided. Use --secret-id or --secret-key');
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
configCmd
|
|
129
|
+
.command('show')
|
|
130
|
+
.description('Show current configuration')
|
|
131
|
+
.action(() => {
|
|
132
|
+
const secretId = getConfig('secretId');
|
|
133
|
+
const secretKey = getConfig('secretKey');
|
|
134
|
+
const hasToken = !!getConfig('accessToken');
|
|
135
|
+
const tokenExpiry = getConfig('tokenExpiry');
|
|
136
|
+
|
|
137
|
+
console.log(chalk.bold('\nNordigen CLI Configuration\n'));
|
|
138
|
+
console.log('Secret ID: ', secretId ? chalk.green(secretId) : chalk.red('not set'));
|
|
139
|
+
console.log('Secret Key: ', secretKey ? chalk.green('*'.repeat(8)) : chalk.red('not set'));
|
|
140
|
+
console.log('Access Token: ', hasToken ? chalk.green('set') : chalk.red('not set'));
|
|
141
|
+
if (tokenExpiry) {
|
|
142
|
+
const expiry = new Date(tokenExpiry);
|
|
143
|
+
const isValid = tokenExpiry > Date.now();
|
|
144
|
+
console.log('Token Expiry: ', isValid ? chalk.green(expiry.toLocaleString()) : chalk.red(`expired (${expiry.toLocaleString()})`));
|
|
145
|
+
}
|
|
146
|
+
console.log('');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ============================================================
|
|
150
|
+
// AUTH
|
|
151
|
+
// ============================================================
|
|
152
|
+
|
|
153
|
+
const authCmd = program.command('auth').description('Manage authentication');
|
|
154
|
+
|
|
155
|
+
authCmd
|
|
156
|
+
.command('login')
|
|
157
|
+
.description('Authenticate with Nordigen and obtain JWT token')
|
|
158
|
+
.action(async () => {
|
|
159
|
+
if (!isConfigured()) {
|
|
160
|
+
printError('Please configure your credentials first:');
|
|
161
|
+
console.log(chalk.cyan(' nordigencom config set --secret-id <id> --secret-key <key>'));
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
// Import the obtainAccessToken logic directly
|
|
167
|
+
const { obtainAccessToken } = await import('./api.js');
|
|
168
|
+
await withSpinner('Obtaining access token...', async () => {
|
|
169
|
+
// Trigger token fetch by calling an API endpoint
|
|
170
|
+
const institutions = await listInstitutions({ country: 'GB' });
|
|
171
|
+
return institutions;
|
|
172
|
+
});
|
|
173
|
+
printSuccess('Successfully authenticated with Nordigen');
|
|
174
|
+
} catch (error) {
|
|
175
|
+
printError(error.message);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
authCmd
|
|
181
|
+
.command('status')
|
|
182
|
+
.description('Check authentication status')
|
|
183
|
+
.action(() => {
|
|
184
|
+
const hasToken = !!getConfig('accessToken');
|
|
185
|
+
const tokenExpiry = getConfig('tokenExpiry');
|
|
186
|
+
|
|
187
|
+
if (!hasToken) {
|
|
188
|
+
printError('Not authenticated. Run: nordigencom auth login');
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const isValid = tokenExpiry > Date.now();
|
|
193
|
+
if (isValid) {
|
|
194
|
+
printSuccess('Authenticated with Nordigen');
|
|
195
|
+
console.log('Token expires:', new Date(tokenExpiry).toLocaleString());
|
|
196
|
+
} else {
|
|
197
|
+
printError('Token expired. Run: nordigencom auth login');
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// ============================================================
|
|
203
|
+
// INSTITUTIONS
|
|
204
|
+
// ============================================================
|
|
205
|
+
|
|
206
|
+
const institutionsCmd = program.command('institutions').description('Manage financial institutions');
|
|
207
|
+
|
|
208
|
+
institutionsCmd
|
|
209
|
+
.command('list')
|
|
210
|
+
.description('List all available institutions')
|
|
211
|
+
.option('--country <code>', 'Filter by country code (e.g., GB, DE, FR)')
|
|
212
|
+
.option('--json', 'Output as JSON')
|
|
213
|
+
.action(async (options) => {
|
|
214
|
+
requireAuth();
|
|
215
|
+
try {
|
|
216
|
+
const institutions = await withSpinner('Fetching institutions...', () =>
|
|
217
|
+
listInstitutions({ country: options.country })
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
if (options.json) {
|
|
221
|
+
printJson(institutions);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
printTable(institutions, [
|
|
226
|
+
{ key: 'id', label: 'ID' },
|
|
227
|
+
{ key: 'name', label: 'Name' },
|
|
228
|
+
{ key: 'bic', label: 'BIC' },
|
|
229
|
+
{ key: 'transaction_total_days', label: 'Transaction Days' },
|
|
230
|
+
{ key: 'countries', label: 'Countries', format: (v) => Array.isArray(v) ? v.join(', ') : v }
|
|
231
|
+
]);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
printError(error.message);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
institutionsCmd
|
|
239
|
+
.command('get <institution-id>')
|
|
240
|
+
.description('Get details about a specific institution')
|
|
241
|
+
.option('--json', 'Output as JSON')
|
|
242
|
+
.action(async (institutionId, options) => {
|
|
243
|
+
requireAuth();
|
|
244
|
+
try {
|
|
245
|
+
const institution = await withSpinner('Fetching institution...', () => getInstitution(institutionId));
|
|
246
|
+
|
|
247
|
+
if (options.json) {
|
|
248
|
+
printJson(institution);
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log(chalk.bold('\nInstitution Details\n'));
|
|
253
|
+
console.log('ID: ', chalk.cyan(institution.id));
|
|
254
|
+
console.log('Name: ', chalk.bold(institution.name));
|
|
255
|
+
console.log('BIC: ', institution.bic || 'N/A');
|
|
256
|
+
console.log('Transaction Days: ', institution.transaction_total_days || 'N/A');
|
|
257
|
+
console.log('Countries: ', Array.isArray(institution.countries) ? institution.countries.join(', ') : 'N/A');
|
|
258
|
+
console.log('Logo: ', institution.logo || 'N/A');
|
|
259
|
+
} catch (error) {
|
|
260
|
+
printError(error.message);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ============================================================
|
|
266
|
+
// AGREEMENTS
|
|
267
|
+
// ============================================================
|
|
268
|
+
|
|
269
|
+
const agreementsCmd = program.command('agreements').description('Manage end user agreements');
|
|
270
|
+
|
|
271
|
+
agreementsCmd
|
|
272
|
+
.command('list')
|
|
273
|
+
.description('List all agreements')
|
|
274
|
+
.option('--limit <n>', 'Maximum number of results', '100')
|
|
275
|
+
.option('--offset <n>', 'Offset for pagination', '0')
|
|
276
|
+
.option('--json', 'Output as JSON')
|
|
277
|
+
.action(async (options) => {
|
|
278
|
+
requireAuth();
|
|
279
|
+
try {
|
|
280
|
+
const agreements = await withSpinner('Fetching agreements...', () =>
|
|
281
|
+
listAgreements({ limit: parseInt(options.limit), offset: parseInt(options.offset) })
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
if (options.json) {
|
|
285
|
+
printJson(agreements);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
printTable(agreements, [
|
|
290
|
+
{ key: 'id', label: 'ID', format: (v) => v?.substring(0, 8) + '...' },
|
|
291
|
+
{ key: 'institution_id', label: 'Institution' },
|
|
292
|
+
{ key: 'created', label: 'Created', format: (v) => v ? new Date(v).toLocaleDateString() : '' },
|
|
293
|
+
{ key: 'max_historical_days', label: 'Historical Days' },
|
|
294
|
+
{ key: 'access_valid_for_days', label: 'Valid For Days' },
|
|
295
|
+
{ key: 'accepted', label: 'Accepted', format: (v) => v ? new Date(v).toLocaleDateString() : 'No' }
|
|
296
|
+
]);
|
|
297
|
+
} catch (error) {
|
|
298
|
+
printError(error.message);
|
|
299
|
+
process.exit(1);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
agreementsCmd
|
|
304
|
+
.command('get <agreement-id>')
|
|
305
|
+
.description('Get a specific agreement')
|
|
306
|
+
.option('--json', 'Output as JSON')
|
|
307
|
+
.action(async (agreementId, options) => {
|
|
308
|
+
requireAuth();
|
|
309
|
+
try {
|
|
310
|
+
const agreement = await withSpinner('Fetching agreement...', () => getAgreement(agreementId));
|
|
311
|
+
|
|
312
|
+
if (options.json) {
|
|
313
|
+
printJson(agreement);
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
console.log(chalk.bold('\nAgreement Details\n'));
|
|
318
|
+
console.log('ID: ', chalk.cyan(agreement.id));
|
|
319
|
+
console.log('Institution ID: ', agreement.institution_id);
|
|
320
|
+
console.log('Created: ', new Date(agreement.created).toLocaleString());
|
|
321
|
+
console.log('Max Historical Days: ', agreement.max_historical_days);
|
|
322
|
+
console.log('Valid For Days: ', agreement.access_valid_for_days);
|
|
323
|
+
console.log('Accepted: ', agreement.accepted ? new Date(agreement.accepted).toLocaleString() : chalk.yellow('Not accepted'));
|
|
324
|
+
console.log('Access Scope: ', Array.isArray(agreement.access_scope) ? agreement.access_scope.join(', ') : 'All');
|
|
325
|
+
} catch (error) {
|
|
326
|
+
printError(error.message);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
agreementsCmd
|
|
332
|
+
.command('create')
|
|
333
|
+
.description('Create a new end user agreement')
|
|
334
|
+
.requiredOption('--institution-id <id>', 'Institution ID')
|
|
335
|
+
.option('--max-historical-days <n>', 'Maximum historical days', '90')
|
|
336
|
+
.option('--access-valid-for-days <n>', 'Access valid for days', '90')
|
|
337
|
+
.option('--access-scope <scopes>', 'Comma-separated access scopes (balances,details,transactions)')
|
|
338
|
+
.option('--json', 'Output as JSON')
|
|
339
|
+
.action(async (options) => {
|
|
340
|
+
requireAuth();
|
|
341
|
+
const accessScope = options.accessScope ? options.accessScope.split(',') : [];
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
const agreement = await withSpinner('Creating agreement...', () =>
|
|
345
|
+
createAgreement({
|
|
346
|
+
institutionId: options.institutionId,
|
|
347
|
+
maxHistoricalDays: parseInt(options.maxHistoricalDays),
|
|
348
|
+
accessValidForDays: parseInt(options.accessValidForDays),
|
|
349
|
+
accessScope
|
|
350
|
+
})
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
if (options.json) {
|
|
354
|
+
printJson(agreement);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
printSuccess(`Agreement created: ${chalk.bold(agreement.id)}`);
|
|
359
|
+
console.log('Institution ID:', agreement.institution_id);
|
|
360
|
+
console.log('Created: ', new Date(agreement.created).toLocaleString());
|
|
361
|
+
} catch (error) {
|
|
362
|
+
printError(error.message);
|
|
363
|
+
process.exit(1);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
agreementsCmd
|
|
368
|
+
.command('delete <agreement-id>')
|
|
369
|
+
.description('Delete an agreement')
|
|
370
|
+
.action(async (agreementId) => {
|
|
371
|
+
requireAuth();
|
|
372
|
+
try {
|
|
373
|
+
await withSpinner('Deleting agreement...', () => deleteAgreement(agreementId));
|
|
374
|
+
printSuccess(`Agreement ${agreementId} deleted`);
|
|
375
|
+
} catch (error) {
|
|
376
|
+
printError(error.message);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// ============================================================
|
|
382
|
+
// REQUISITIONS
|
|
383
|
+
// ============================================================
|
|
384
|
+
|
|
385
|
+
const requisitionsCmd = program.command('requisitions').description('Manage requisitions (bank connections)');
|
|
386
|
+
|
|
387
|
+
requisitionsCmd
|
|
388
|
+
.command('list')
|
|
389
|
+
.description('List all requisitions')
|
|
390
|
+
.option('--limit <n>', 'Maximum number of results', '100')
|
|
391
|
+
.option('--offset <n>', 'Offset for pagination', '0')
|
|
392
|
+
.option('--json', 'Output as JSON')
|
|
393
|
+
.action(async (options) => {
|
|
394
|
+
requireAuth();
|
|
395
|
+
try {
|
|
396
|
+
const requisitions = await withSpinner('Fetching requisitions...', () =>
|
|
397
|
+
listRequisitions({ limit: parseInt(options.limit), offset: parseInt(options.offset) })
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
if (options.json) {
|
|
401
|
+
printJson(requisitions);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
printTable(requisitions, [
|
|
406
|
+
{ key: 'id', label: 'ID', format: (v) => v?.substring(0, 8) + '...' },
|
|
407
|
+
{ key: 'reference', label: 'Reference' },
|
|
408
|
+
{ key: 'status', label: 'Status' },
|
|
409
|
+
{ key: 'institution_id', label: 'Institution' },
|
|
410
|
+
{ key: 'created', label: 'Created', format: (v) => v ? new Date(v).toLocaleDateString() : '' },
|
|
411
|
+
{ key: 'accounts', label: 'Accounts', format: (v) => Array.isArray(v) ? v.length : 0 }
|
|
412
|
+
]);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
printError(error.message);
|
|
415
|
+
process.exit(1);
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
requisitionsCmd
|
|
420
|
+
.command('get <requisition-id>')
|
|
421
|
+
.description('Get a specific requisition')
|
|
422
|
+
.option('--json', 'Output as JSON')
|
|
423
|
+
.action(async (requisitionId, options) => {
|
|
424
|
+
requireAuth();
|
|
425
|
+
try {
|
|
426
|
+
const requisition = await withSpinner('Fetching requisition...', () => getRequisition(requisitionId));
|
|
427
|
+
|
|
428
|
+
if (options.json) {
|
|
429
|
+
printJson(requisition);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
console.log(chalk.bold('\nRequisition Details\n'));
|
|
434
|
+
console.log('ID: ', chalk.cyan(requisition.id));
|
|
435
|
+
console.log('Reference: ', requisition.reference);
|
|
436
|
+
console.log('Status: ', chalk.bold(requisition.status));
|
|
437
|
+
console.log('Institution: ', requisition.institution_id);
|
|
438
|
+
console.log('Created: ', new Date(requisition.created).toLocaleString());
|
|
439
|
+
console.log('Link: ', requisition.link || 'N/A');
|
|
440
|
+
console.log('Accounts: ', Array.isArray(requisition.accounts) ? requisition.accounts.join(', ') : 'None');
|
|
441
|
+
} catch (error) {
|
|
442
|
+
printError(error.message);
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
requisitionsCmd
|
|
448
|
+
.command('create')
|
|
449
|
+
.description('Create a new requisition')
|
|
450
|
+
.requiredOption('--institution-id <id>', 'Institution ID')
|
|
451
|
+
.requiredOption('--redirect <url>', 'Redirect URL after authentication')
|
|
452
|
+
.requiredOption('--reference <ref>', 'Unique reference for this requisition')
|
|
453
|
+
.option('--agreement-id <id>', 'End user agreement ID')
|
|
454
|
+
.option('--user-language <lang>', 'User language (EN, DE, FR, etc.)', 'EN')
|
|
455
|
+
.option('--json', 'Output as JSON')
|
|
456
|
+
.action(async (options) => {
|
|
457
|
+
requireAuth();
|
|
458
|
+
try {
|
|
459
|
+
const requisition = await withSpinner('Creating requisition...', () =>
|
|
460
|
+
createRequisition({
|
|
461
|
+
institutionId: options.institutionId,
|
|
462
|
+
redirect: options.redirect,
|
|
463
|
+
reference: options.reference,
|
|
464
|
+
agreementId: options.agreementId,
|
|
465
|
+
userLanguage: options.userLanguage
|
|
466
|
+
})
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
if (options.json) {
|
|
470
|
+
printJson(requisition);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
printSuccess(`Requisition created: ${chalk.bold(requisition.id)}`);
|
|
475
|
+
console.log('Reference: ', requisition.reference);
|
|
476
|
+
console.log('Status: ', requisition.status);
|
|
477
|
+
console.log('Link: ', chalk.cyan(requisition.link));
|
|
478
|
+
console.log('\nSend this link to the user to authorize bank access.');
|
|
479
|
+
} catch (error) {
|
|
480
|
+
printError(error.message);
|
|
481
|
+
process.exit(1);
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
requisitionsCmd
|
|
486
|
+
.command('delete <requisition-id>')
|
|
487
|
+
.description('Delete a requisition')
|
|
488
|
+
.action(async (requisitionId) => {
|
|
489
|
+
requireAuth();
|
|
490
|
+
try {
|
|
491
|
+
await withSpinner('Deleting requisition...', () => deleteRequisition(requisitionId));
|
|
492
|
+
printSuccess(`Requisition ${requisitionId} deleted`);
|
|
493
|
+
} catch (error) {
|
|
494
|
+
printError(error.message);
|
|
495
|
+
process.exit(1);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// ============================================================
|
|
500
|
+
// ACCOUNTS
|
|
501
|
+
// ============================================================
|
|
502
|
+
|
|
503
|
+
const accountsCmd = program.command('accounts').description('Access account information');
|
|
504
|
+
|
|
505
|
+
accountsCmd
|
|
506
|
+
.command('get <account-id>')
|
|
507
|
+
.description('Get account metadata')
|
|
508
|
+
.option('--json', 'Output as JSON')
|
|
509
|
+
.action(async (accountId, options) => {
|
|
510
|
+
requireAuth();
|
|
511
|
+
try {
|
|
512
|
+
const account = await withSpinner('Fetching account metadata...', () => getAccountMetadata(accountId));
|
|
513
|
+
|
|
514
|
+
if (options.json) {
|
|
515
|
+
printJson(account);
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
console.log(chalk.bold('\nAccount Metadata\n'));
|
|
520
|
+
console.log('Account ID: ', chalk.cyan(account.id));
|
|
521
|
+
console.log('IBAN: ', account.iban || 'N/A');
|
|
522
|
+
console.log('Institution: ', account.institution_id);
|
|
523
|
+
console.log('Created: ', new Date(account.created).toLocaleString());
|
|
524
|
+
console.log('Status: ', account.status);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
printError(error.message);
|
|
527
|
+
process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
accountsCmd
|
|
532
|
+
.command('balances <account-id>')
|
|
533
|
+
.description('Get account balances')
|
|
534
|
+
.option('--json', 'Output as JSON')
|
|
535
|
+
.action(async (accountId, options) => {
|
|
536
|
+
requireAuth();
|
|
537
|
+
try {
|
|
538
|
+
const data = await withSpinner('Fetching balances...', () => getAccountBalances(accountId));
|
|
539
|
+
|
|
540
|
+
if (options.json) {
|
|
541
|
+
printJson(data);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
const balances = data.balances || [];
|
|
546
|
+
if (balances.length === 0) {
|
|
547
|
+
console.log(chalk.yellow('No balances found.'));
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
console.log(chalk.bold('\nAccount Balances\n'));
|
|
552
|
+
balances.forEach(balance => {
|
|
553
|
+
console.log(`${balance.balanceType || 'Balance'}:`);
|
|
554
|
+
console.log(` Amount: ${balance.balanceAmount?.amount} ${balance.balanceAmount?.currency}`);
|
|
555
|
+
console.log(` Date: ${balance.referenceDate || 'N/A'}`);
|
|
556
|
+
console.log('');
|
|
557
|
+
});
|
|
558
|
+
} catch (error) {
|
|
559
|
+
printError(error.message);
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
accountsCmd
|
|
565
|
+
.command('details <account-id>')
|
|
566
|
+
.description('Get account details')
|
|
567
|
+
.option('--json', 'Output as JSON')
|
|
568
|
+
.action(async (accountId, options) => {
|
|
569
|
+
requireAuth();
|
|
570
|
+
try {
|
|
571
|
+
const data = await withSpinner('Fetching account details...', () => getAccountDetails(accountId));
|
|
572
|
+
|
|
573
|
+
if (options.json) {
|
|
574
|
+
printJson(data);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
const account = data.account || {};
|
|
579
|
+
console.log(chalk.bold('\nAccount Details\n'));
|
|
580
|
+
console.log('IBAN: ', account.iban || 'N/A');
|
|
581
|
+
console.log('Name: ', account.name || 'N/A');
|
|
582
|
+
console.log('Currency: ', account.currency || 'N/A');
|
|
583
|
+
console.log('Owner Name: ', account.ownerName || 'N/A');
|
|
584
|
+
console.log('Product: ', account.product || 'N/A');
|
|
585
|
+
} catch (error) {
|
|
586
|
+
printError(error.message);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
accountsCmd
|
|
592
|
+
.command('transactions <account-id>')
|
|
593
|
+
.description('Get account transactions')
|
|
594
|
+
.option('--json', 'Output as JSON')
|
|
595
|
+
.action(async (accountId, options) => {
|
|
596
|
+
requireAuth();
|
|
597
|
+
try {
|
|
598
|
+
const data = await withSpinner('Fetching transactions...', () => getAccountTransactions(accountId));
|
|
599
|
+
|
|
600
|
+
if (options.json) {
|
|
601
|
+
printJson(data);
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
const transactions = data.transactions?.booked || [];
|
|
606
|
+
if (transactions.length === 0) {
|
|
607
|
+
console.log(chalk.yellow('No transactions found.'));
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
printTable(transactions, [
|
|
612
|
+
{ key: 'transactionId', label: 'Transaction ID', format: (v) => v?.substring(0, 12) + '...' },
|
|
613
|
+
{ key: 'bookingDate', label: 'Date' },
|
|
614
|
+
{ key: 'transactionAmount', label: 'Amount', format: (v) => `${v?.amount} ${v?.currency}` },
|
|
615
|
+
{ key: 'creditorName', label: 'Creditor', format: (v) => v || 'N/A' },
|
|
616
|
+
{ key: 'debtorName', label: 'Debtor', format: (v) => v || 'N/A' },
|
|
617
|
+
{ key: 'remittanceInformationUnstructured', label: 'Description', format: (v) => v?.substring(0, 30) || 'N/A' }
|
|
618
|
+
]);
|
|
619
|
+
} catch (error) {
|
|
620
|
+
printError(error.message);
|
|
621
|
+
process.exit(1);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// ============================================================
|
|
626
|
+
// Parse
|
|
627
|
+
// ============================================================
|
|
628
|
+
|
|
629
|
+
program.parse(process.argv);
|
|
630
|
+
|
|
631
|
+
if (process.argv.length <= 2) {
|
|
632
|
+
program.help();
|
|
633
|
+
}
|
package/.env.example
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# Nordigen API Credentials
|
|
2
|
-
# Get these from https://nordigen.com dashboard
|
|
3
|
-
|
|
4
|
-
NORDIGEN_SECRET_ID=your_secret_id_here
|
|
5
|
-
NORDIGEN_SECRET_KEY=your_secret_key_here
|
|
6
|
-
|
|
7
|
-
# Optional: Default country for commands
|
|
8
|
-
NORDIGEN_DEFAULT_COUNTRY=GB
|
|
9
|
-
|
|
10
|
-
# Optional: Enable debug mode
|
|
11
|
-
# DEBUG=1
|
package/.eslintrc.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"es2021": true,
|
|
4
|
-
"node": true
|
|
5
|
-
},
|
|
6
|
-
"extends": "eslint:recommended",
|
|
7
|
-
"parserOptions": {
|
|
8
|
-
"ecmaVersion": "latest",
|
|
9
|
-
"sourceType": "module"
|
|
10
|
-
},
|
|
11
|
-
"rules": {
|
|
12
|
-
"indent": ["error", 2],
|
|
13
|
-
"linebreak-style": ["error", "unix"],
|
|
14
|
-
"quotes": ["error", "single"],
|
|
15
|
-
"semi": ["error", "always"]
|
|
16
|
-
}
|
|
17
|
-
}
|