@ktmcp-cli/nordigen 1.0.1 → 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 CHANGED
@@ -1,480 +1,199 @@
1
- # Nordigen CLI - AI Agent Integration Guide
1
+ # AGENT.md — Nordigen CLI for AI Agents
2
2
 
3
- This guide shows how AI agents can effectively use the Nordigen CLI to access open banking data.
3
+ This document explains how to use the Nordigen CLI as an AI agent.
4
4
 
5
- ## Agent Capabilities
5
+ ## Overview
6
6
 
7
- The Nordigen CLI enables AI agents to:
7
+ The `nordigencom` CLI provides access to the Nordigen Account Information Services API. Use it to access bank account data, transactions, balances, and manage open banking connections on behalf of users.
8
8
 
9
- 1. Browse and search financial institutions across Europe
10
- 2. Create and manage end user agreements
11
- 3. Initiate bank account connections
12
- 4. Retrieve account balances and details
13
- 5. Fetch and analyze transaction history
14
- 6. Process payment information
9
+ ## Prerequisites
15
10
 
16
- ## Basic Agent Patterns
11
+ The CLI must be authenticated before use. Check status with:
17
12
 
18
- ### Pattern 1: Institution Discovery
19
-
20
- ```javascript
21
- // Agent discovers banks in a specific country
22
- async function findInstitutions(country, query = null) {
23
- if (query) {
24
- // Search for specific institution
25
- const result = await exec(`nordigen institutions search "${query}" --country ${country} --json`);
26
- return JSON.parse(result.stdout);
27
- } else {
28
- // List all institutions
29
- const result = await exec(`nordigen institutions list --country ${country} --json`);
30
- return JSON.parse(result.stdout);
31
- }
32
- }
33
-
34
- // Usage
35
- const banks = await findInstitutions('GB', 'Barclays');
13
+ ```bash
14
+ nordigencom auth status
36
15
  ```
37
16
 
38
- ### Pattern 2: Account Connection Flow
39
-
40
- ```javascript
41
- // Agent guides user through bank connection
42
- async function connectBankAccount(institutionId, redirectUrl) {
43
- // 1. Create end user agreement
44
- const agreementCmd = `nordigen agreements create --institution-id ${institutionId} --max-days 90 --json`;
45
- const agreement = JSON.parse((await exec(agreementCmd)).stdout);
46
-
47
- // 2. Create requisition
48
- const reqCmd = `nordigen requisitions create --institution-id ${institutionId} --redirect ${redirectUrl} --agreement ${agreement.id} --json`;
49
- const requisition = JSON.parse((await exec(reqCmd)).stdout);
50
-
51
- // 3. Return authentication link for user
52
- return {
53
- requisitionId: requisition.id,
54
- authLink: requisition.link,
55
- status: requisition.status
56
- };
57
- }
17
+ If not authenticated, the user must run:
18
+ ```bash
19
+ nordigencom config set --secret-id <id> --secret-key <key>
20
+ nordigencom auth login
58
21
  ```
59
22
 
60
- ### Pattern 3: Transaction Analysis
61
-
62
- ```javascript
63
- // Agent retrieves and analyzes transactions
64
- async function analyzeTransactions(accountId, dateFrom, dateTo) {
65
- const cmd = `nordigen accounts transactions ${accountId} --from ${dateFrom} --to ${dateTo} --json`;
66
- const result = await exec(cmd);
67
- const data = JSON.parse(result.stdout);
68
-
69
- const transactions = data.transactions.booked || [];
70
-
71
- // Analyze spending patterns
72
- const analysis = {
73
- totalSpent: 0,
74
- totalIncome: 0,
75
- categories: {},
76
- transactionCount: transactions.length
77
- };
78
-
79
- transactions.forEach(tx => {
80
- const amount = parseFloat(tx.transactionAmount.amount);
81
- if (amount < 0) {
82
- analysis.totalSpent += Math.abs(amount);
83
- } else {
84
- analysis.totalIncome += amount;
85
- }
86
- });
87
-
88
- return analysis;
89
- }
90
- ```
23
+ ## All Commands
24
+
25
+ ### Config
91
26
 
92
- ### Pattern 4: Multi-Account Balance Check
93
-
94
- ```javascript
95
- // Agent checks balances across multiple accounts
96
- async function checkAllBalances(accountIds) {
97
- const balances = await Promise.all(
98
- accountIds.map(async (accountId) => {
99
- try {
100
- const cmd = `nordigen accounts balances ${accountId} --json`;
101
- const result = await exec(cmd);
102
- const data = JSON.parse(result.stdout);
103
-
104
- return {
105
- accountId,
106
- balances: data.balances,
107
- success: true
108
- };
109
- } catch (error) {
110
- return {
111
- accountId,
112
- error: error.message,
113
- success: false
114
- };
115
- }
116
- })
117
- );
118
-
119
- return balances;
120
- }
27
+ ```bash
28
+ nordigencom config set --secret-id <id> --secret-key <key>
29
+ nordigencom config show
121
30
  ```
122
31
 
123
- ## Advanced Agent Workflows
124
-
125
- ### Workflow 1: Automated Financial Health Check
126
-
127
- ```javascript
128
- async function financialHealthCheck(userId) {
129
- // 1. Get all user's requisitions
130
- const reqList = await exec('nordigen requisitions list --json');
131
- const requisitions = JSON.parse(reqList.stdout).results;
132
-
133
- // 2. Extract all account IDs
134
- const accountIds = requisitions.flatMap(req => req.accounts);
135
-
136
- // 3. Check balances
137
- const balances = await checkAllBalances(accountIds);
138
-
139
- // 4. Get recent transactions (last 30 days)
140
- const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)
141
- .toISOString().split('T')[0];
142
- const today = new Date().toISOString().split('T')[0];
143
-
144
- const transactionAnalysis = await Promise.all(
145
- accountIds.map(id => analyzeTransactions(id, thirtyDaysAgo, today))
146
- );
147
-
148
- // 5. Generate report
149
- return {
150
- totalAccounts: accountIds.length,
151
- totalBalance: balances.reduce((sum, b) => {
152
- const balance = b.balances?.find(bal => bal.balanceType === 'expected')?.balanceAmount?.amount || 0;
153
- return sum + parseFloat(balance);
154
- }, 0),
155
- monthlySpending: transactionAnalysis.reduce((sum, a) => sum + a.totalSpent, 0),
156
- monthlyIncome: transactionAnalysis.reduce((sum, a) => sum + a.totalIncome, 0)
157
- };
158
- }
32
+ ### Auth
33
+
34
+ ```bash
35
+ nordigencom auth login # Obtain JWT token
36
+ nordigencom auth status # Check if authenticated
159
37
  ```
160
38
 
161
- ### Workflow 2: Smart Institution Recommendation
39
+ ### Institutions
162
40
 
163
- ```javascript
164
- async function recommendInstitution(country, requirements) {
165
- // Get all institutions
166
- const cmd = `nordigen institutions list --country ${country} --json`;
167
- const institutions = JSON.parse((await exec(cmd)).stdout);
41
+ ```bash
42
+ # List institutions
43
+ nordigencom institutions list
44
+ nordigencom institutions list --country GB
45
+ nordigencom institutions list --country DE
46
+ nordigencom institutions list --country FR
168
47
 
169
- // Score institutions based on requirements
170
- const scored = institutions.map(inst => {
171
- let score = 0;
48
+ # Get single institution
49
+ nordigencom institutions get <institution-id>
50
+ ```
172
51
 
173
- // Prefer institutions with longer transaction history
174
- if (inst.transaction_total_days >= 730) score += 3;
175
- else if (inst.transaction_total_days >= 365) score += 2;
176
- else if (inst.transaction_total_days >= 90) score += 1;
52
+ Country codes: GB, DE, FR, ES, IT, NL, BE, AT, SE, DK, NO, FI, IE, PT, PL, CZ, EE, LV, LT, LU, SI, SK, MT, CY, BG, RO, HR, GR
177
53
 
178
- // Check payment support if required
179
- if (requirements.payments && inst.payments_enabled) score += 2;
54
+ ### Agreements
180
55
 
181
- // Check account selection if required
182
- if (requirements.accountSelection && inst.account_selection_supported) score += 1;
56
+ ```bash
57
+ # List agreements
58
+ nordigencom agreements list
59
+ nordigencom agreements list --limit 50 --offset 0
183
60
 
184
- return { ...inst, score };
185
- });
61
+ # Get single agreement
62
+ nordigencom agreements get <agreement-id>
186
63
 
187
- // Sort by score and return top recommendations
188
- return scored
189
- .sort((a, b) => b.score - a.score)
190
- .slice(0, 5);
191
- }
192
- ```
64
+ # Create agreement
65
+ nordigencom agreements create \
66
+ --institution-id <institution-id> \
67
+ --max-historical-days 90 \
68
+ --access-valid-for-days 90 \
69
+ --access-scope balances,details,transactions
193
70
 
194
- ### Workflow 3: Transaction Categorization
195
-
196
- ```javascript
197
- async function categorizeTransactions(accountId, dateFrom, dateTo) {
198
- const cmd = `nordigen accounts transactions ${accountId} --from ${dateFrom} --to ${dateTo} --json`;
199
- const result = await exec(cmd);
200
- const data = JSON.parse(result.stdout);
201
-
202
- const transactions = data.transactions.booked || [];
203
-
204
- // Simple rule-based categorization
205
- const categories = {
206
- groceries: /tesco|sainsbury|asda|morrisons|aldi|lidl/i,
207
- transport: /uber|lyft|tfl|national rail|trainline/i,
208
- utilities: /electricity|gas|water|internet|broadband/i,
209
- entertainment: /netflix|spotify|amazon prime|cinema/i,
210
- restaurants: /restaurant|cafe|pizza|burger|mcdonald/i
211
- };
212
-
213
- const categorized = transactions.map(tx => {
214
- const description = tx.remittanceInformationUnstructured || '';
215
- let category = 'other';
216
-
217
- for (const [cat, pattern] of Object.entries(categories)) {
218
- if (pattern.test(description)) {
219
- category = cat;
220
- break;
221
- }
222
- }
223
-
224
- return {
225
- ...tx,
226
- category,
227
- amount: parseFloat(tx.transactionAmount.amount)
228
- };
229
- });
230
-
231
- // Summarize by category
232
- const summary = {};
233
- categorized.forEach(tx => {
234
- if (!summary[tx.category]) {
235
- summary[tx.category] = { count: 0, total: 0 };
236
- }
237
- summary[tx.category].count++;
238
- summary[tx.category].total += Math.abs(tx.amount);
239
- });
240
-
241
- return { transactions: categorized, summary };
242
- }
71
+ # Delete agreement
72
+ nordigencom agreements delete <agreement-id>
243
73
  ```
244
74
 
245
- ## Natural Language Interface
75
+ Access scopes: `balances`, `details`, `transactions`
246
76
 
247
- ### Example: Agent Conversation Flow
77
+ ### Requisitions
248
78
 
79
+ ```bash
80
+ # List requisitions
81
+ nordigencom requisitions list
82
+ nordigencom requisitions list --limit 50 --offset 0
83
+
84
+ # Get single requisition
85
+ nordigencom requisitions get <requisition-id>
86
+
87
+ # Create requisition
88
+ nordigencom requisitions create \
89
+ --institution-id <institution-id> \
90
+ --redirect <callback-url> \
91
+ --reference <unique-reference> \
92
+ --agreement-id <agreement-id> \
93
+ --user-language EN
94
+
95
+ # Delete requisition
96
+ nordigencom requisitions delete <requisition-id>
249
97
  ```
250
- User: "Connect my Barclays account"
251
98
 
252
- Agent Analysis:
253
- 1. User wants to connect a bank account
254
- 2. Bank name: "Barclays"
255
- 3. Need to determine country (ask or infer from context)
99
+ User languages: EN, DE, FR, ES, IT, NL, etc.
100
+
101
+ ### Accounts
256
102
 
257
- Agent Response:
258
- "I'll help you connect your Barclays account. Which country is your Barclays account in? (UK, Germany, etc.)"
103
+ ```bash
104
+ # Get account metadata
105
+ nordigencom accounts get <account-id>
259
106
 
260
- User: "UK"
107
+ # Get account balances
108
+ nordigencom accounts balances <account-id>
261
109
 
262
- Agent Actions:
263
- 1. nordigen institutions search "Barclays" --country GB --json
264
- 2. Select appropriate institution ID
265
- 3. nordigen agreements create --institution-id BARCLAYS_BARCGB22 --max-days 90 --json
266
- 4. nordigen requisitions create --institution-id BARCLAYS_BARCGB22 --redirect https://app.example.com/callback --agreement <ID> --json
110
+ # Get account details
111
+ nordigencom accounts details <account-id>
267
112
 
268
- Agent Response:
269
- "I've created a secure connection request. Please visit this link to authorize access to your Barclays account: [AUTH_LINK]"
113
+ # Get account transactions
114
+ nordigencom accounts transactions <account-id>
270
115
  ```
271
116
 
272
- ### Example: Transaction Query
117
+ ## JSON Output
273
118
 
274
- ```
275
- User: "How much did I spend on groceries last month?"
276
-
277
- Agent Actions:
278
- 1. Calculate date range (last month)
279
- 2. Get user's account IDs from stored requisitions
280
- 3. For each account:
281
- - nordigen accounts transactions <ID> --from 2024-01-01 --to 2024-01-31 --json
282
- 4. Categorize transactions
283
- 5. Sum amounts in "groceries" category
284
-
285
- Agent Response:
286
- "You spent £342.67 on groceries last month across 23 transactions. The main stores were Tesco (£178.42), Sainsbury's (£121.30), and Aldi (£42.95)."
287
- ```
119
+ All list and get commands support `--json` for structured output. Always use `--json` when parsing results programmatically:
288
120
 
289
- ## Error Handling for Agents
290
-
291
- ```javascript
292
- async function safeExecute(command) {
293
- try {
294
- const result = await exec(command);
295
- return { success: true, data: JSON.parse(result.stdout) };
296
- } catch (error) {
297
- // Parse error message
298
- const errorData = {
299
- success: false,
300
- command: command,
301
- exitCode: error.code
302
- };
303
-
304
- // Check for common errors
305
- if (error.stderr.includes('Not authenticated')) {
306
- errorData.type = 'AUTH_REQUIRED';
307
- errorData.message = 'User needs to authenticate first';
308
- errorData.action = 'Please run: nordigen auth login --secret-id <id> --secret-key <key>';
309
- } else if (error.stderr.includes('Token is invalid or expired')) {
310
- errorData.type = 'TOKEN_EXPIRED';
311
- errorData.message = 'Authentication token expired';
312
- errorData.action = 'Re-authenticating automatically...';
313
- } else if (error.stderr.includes('Rate limit')) {
314
- errorData.type = 'RATE_LIMIT';
315
- errorData.message = 'API rate limit exceeded';
316
- errorData.action = 'Waiting before retry...';
317
- } else {
318
- errorData.type = 'UNKNOWN';
319
- errorData.message = error.stderr;
320
- }
321
-
322
- return errorData;
323
- }
324
- }
121
+ ```bash
122
+ nordigencom institutions list --json
123
+ nordigencom agreements list --json
124
+ nordigencom requisitions list --json
125
+ nordigencom accounts transactions <account-id> --json
325
126
  ```
326
127
 
327
- ## Security Best Practices
328
-
329
- 1. **Never log sensitive data**: Avoid logging full API responses that may contain IBANs or account details
330
- 2. **Use environment variables**: Store credentials in env vars, not code
331
- 3. **Implement access controls**: Ensure agents can only access data for authorized users
332
- 4. **Audit agent actions**: Log all CLI commands executed by agents
333
- 5. **Time-limited access**: Regularly refresh agreements with minimal access duration
334
-
335
- ## Agent State Management
336
-
337
- ```javascript
338
- class NordigenAgent {
339
- constructor(userId) {
340
- this.userId = userId;
341
- this.state = {
342
- authenticated: false,
343
- activeRequisitions: [],
344
- accountIds: [],
345
- lastSync: null
346
- };
347
- }
348
-
349
- async initialize() {
350
- // Check authentication
351
- const authStatus = await exec('nordigen auth status --json');
352
- this.state.authenticated = JSON.parse(authStatus.stdout).authenticated;
353
-
354
- if (this.state.authenticated) {
355
- await this.syncRequisitions();
356
- }
357
- }
358
-
359
- async syncRequisitions() {
360
- const result = await exec('nordigen requisitions list --json');
361
- const data = JSON.parse(result.stdout);
362
- this.state.activeRequisitions = data.results;
363
- this.state.accountIds = data.results.flatMap(r => r.accounts);
364
- this.state.lastSync = new Date().toISOString();
365
- }
366
-
367
- async handleQuery(userInput) {
368
- // Parse user intent
369
- const intent = this.parseIntent(userInput);
370
-
371
- switch (intent.type) {
372
- case 'GET_BALANCE':
373
- return await this.getBalances();
374
- case 'GET_TRANSACTIONS':
375
- return await this.getTransactions(intent.params);
376
- case 'CONNECT_ACCOUNT':
377
- return await this.connectAccount(intent.params);
378
- default:
379
- return { error: 'Unknown intent' };
380
- }
381
- }
382
- }
128
+ ## Example Workflows
129
+
130
+ ### Connect a new bank account
131
+
132
+ ```bash
133
+ # Step 1: Find the institution
134
+ nordigencom institutions list --country GB --json | jq '.[] | select(.name | contains("Revolut"))'
135
+
136
+ # Step 2: Create an agreement
137
+ nordigencom agreements create \
138
+ --institution-id REVOLUT_REVOGB21 \
139
+ --max-historical-days 90 \
140
+ --access-valid-for-days 90 \
141
+ --json
142
+
143
+ # Step 3: Create a requisition (use agreement ID from step 2)
144
+ nordigencom requisitions create \
145
+ --institution-id REVOLUT_REVOGB21 \
146
+ --redirect https://myapp.com/callback \
147
+ --reference user-123 \
148
+ --agreement-id <agreement-id> \
149
+ --json
150
+
151
+ # Step 4: Send the "link" URL to the user for authorization
152
+ # After authorization, get the requisition to see account IDs
153
+
154
+ nordigencom requisitions get <requisition-id> --json
383
155
  ```
384
156
 
385
- ## Performance Optimization
386
-
387
- 1. **Cache institution lists**: Store institution data locally, refresh periodically
388
- 2. **Batch operations**: Use Promise.all() for parallel account queries
389
- 3. **Pagination**: Use --limit and --offset for large result sets
390
- 4. **Conditional fetching**: Only fetch data when needed, not proactively
391
-
392
- ## Testing Agent Integration
393
-
394
- ```javascript
395
- // Mock CLI responses for testing
396
- const mockCLI = {
397
- 'nordigen auth status': { authenticated: true },
398
- 'nordigen institutions list --country GB --json': [
399
- { id: 'BARCLAYS_BARCGB22', name: 'Barclays' }
400
- ]
401
- };
402
-
403
- async function testAgentFlow() {
404
- // Test institution discovery
405
- const banks = await findInstitutions('GB', 'Barclays');
406
- assert(banks.length > 0);
407
-
408
- // Test error handling
409
- const result = await safeExecute('nordigen accounts get invalid-id --json');
410
- assert(result.success === false);
411
-
412
- // Test transaction analysis
413
- const analysis = await analyzeTransactions('test-account', '2024-01-01', '2024-01-31');
414
- assert(analysis.transactionCount >= 0);
415
- }
157
+ ### Retrieve account transactions
158
+
159
+ ```bash
160
+ # Get all booked transactions as JSON
161
+ nordigencom accounts transactions <account-id> --json | jq '.transactions.booked'
162
+
163
+ # Filter transactions by amount
164
+ nordigencom accounts transactions <account-id> --json | jq '.transactions.booked[] | select(.transactionAmount.amount | tonumber > 100)'
416
165
  ```
417
166
 
418
- ## Example: Claude/GPT Integration
419
-
420
- ```python
421
- import subprocess
422
- import json
423
-
424
- def run_nordigen_command(command):
425
- """Execute Nordigen CLI command and return JSON output."""
426
- result = subprocess.run(
427
- command,
428
- shell=True,
429
- capture_output=True,
430
- text=True
431
- )
432
-
433
- if result.returncode != 0:
434
- raise Exception(f"Command failed: {result.stderr}")
435
-
436
- return json.loads(result.stdout)
437
-
438
- # Agent function
439
- def get_account_summary(account_id):
440
- """Get comprehensive account summary."""
441
-
442
- # Get balances
443
- balances = run_nordigen_command(
444
- f"nordigen accounts balances {account_id} --json"
445
- )
446
-
447
- # Get transactions (last 30 days)
448
- from datetime import datetime, timedelta
449
- end_date = datetime.now().strftime('%Y-%m-%d')
450
- start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
451
-
452
- transactions = run_nordigen_command(
453
- f"nordigen accounts transactions {account_id} --from {start_date} --to {end_date} --json"
454
- )
455
-
456
- # Format for LLM context
457
- return {
458
- "account_id": account_id,
459
- "balances": balances["balances"],
460
- "recent_transactions": len(transactions["transactions"]["booked"]),
461
- "transaction_data": transactions
462
- }
463
-
464
- # Use in prompt
465
- account_summary = get_account_summary("abc-123")
466
- prompt = f"""
467
- Analyze this bank account and provide insights:
468
-
469
- {json.dumps(account_summary, indent=2)}
470
-
471
- Please provide:
472
- 1. Current financial position
473
- 2. Spending patterns
474
- 3. Recommendations
475
- """
167
+ ### Monitor account balances
168
+
169
+ ```bash
170
+ # Get current balance
171
+ nordigencom accounts balances <account-id> --json | jq '.balances[] | select(.balanceType == "interimAvailable")'
476
172
  ```
477
173
 
478
- ## Conclusion
174
+ ## Requisition Flow
175
+
176
+ The typical flow for connecting a bank account:
177
+
178
+ 1. **Create Agreement** — Define what data you need and for how long
179
+ 2. **Create Requisition** — Generate an authorization link
180
+ 3. **User Authorizes** — User clicks the link and authenticates with their bank
181
+ 4. **Retrieve Accounts** — After authorization, get account IDs from the requisition
182
+ 5. **Access Data** — Use account IDs to fetch balances, details, and transactions
183
+
184
+ ## Error Handling
185
+
186
+ The CLI exits with code 1 on error and prints an error message to stderr. Common errors:
187
+
188
+ - `Authentication failed` — Run `nordigencom auth login`
189
+ - `Resource not found` — Check the ID is correct
190
+ - `Rate limit exceeded` — Wait before retrying
191
+
192
+ ## Tips for Agents
479
193
 
480
- The Nordigen CLI provides a powerful, simple interface for AI agents to interact with open banking APIs. By following these patterns and best practices, agents can provide sophisticated financial analysis and account management capabilities.
194
+ 1. Always use `--json` when you need to extract specific fields
195
+ 2. When creating requisitions, always provide a unique reference (e.g., user ID)
196
+ 3. Store agreement IDs and requisition IDs for later use
197
+ 4. The authorization link expires after a period, so create new requisitions as needed
198
+ 5. JWT tokens are automatically refreshed — no need to re-authenticate unless credentials expire
199
+ 6. Use country codes to filter institutions for better UX
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Tom Granot
3
+ Copyright (c) 2024 KTMCP
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal