@lunchflow/actual-flow 0.0.1

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.
Files changed (45) hide show
  1. package/README.md +196 -0
  2. package/dist/actual-budget-client.d.ts +19 -0
  3. package/dist/actual-budget-client.d.ts.map +1 -0
  4. package/dist/actual-budget-client.js +161 -0
  5. package/dist/actual-budget-client.js.map +1 -0
  6. package/dist/config-manager.d.ts +16 -0
  7. package/dist/config-manager.d.ts.map +1 -0
  8. package/dist/config-manager.js +119 -0
  9. package/dist/config-manager.js.map +1 -0
  10. package/dist/importer.d.ts +20 -0
  11. package/dist/importer.d.ts.map +1 -0
  12. package/dist/importer.js +234 -0
  13. package/dist/importer.js.map +1 -0
  14. package/dist/index.d.ts +3 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +30 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/lunch-flow-client.d.ts +10 -0
  19. package/dist/lunch-flow-client.d.ts.map +1 -0
  20. package/dist/lunch-flow-client.js +67 -0
  21. package/dist/lunch-flow-client.js.map +1 -0
  22. package/dist/transaction-mapper.d.ts +8 -0
  23. package/dist/transaction-mapper.d.ts.map +1 -0
  24. package/dist/transaction-mapper.js +32 -0
  25. package/dist/transaction-mapper.js.map +1 -0
  26. package/dist/types.d.ts +55 -0
  27. package/dist/types.d.ts.map +1 -0
  28. package/dist/types.js +3 -0
  29. package/dist/types.js.map +1 -0
  30. package/dist/ui.d.ts +30 -0
  31. package/dist/ui.d.ts.map +1 -0
  32. package/dist/ui.js +278 -0
  33. package/dist/ui.js.map +1 -0
  34. package/install.sh +55 -0
  35. package/package.json +35 -0
  36. package/pnpm-workspace.yaml +2 -0
  37. package/src/actual-budget-client.ts +132 -0
  38. package/src/config-manager.ts +128 -0
  39. package/src/importer.ts +279 -0
  40. package/src/index.ts +28 -0
  41. package/src/lunch-flow-client.ts +64 -0
  42. package/src/transaction-mapper.ts +37 -0
  43. package/src/types.ts +60 -0
  44. package/src/ui.ts +314 -0
  45. package/tsconfig.json +19 -0
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # Lunch Flow → Actual Budget Importer
2
+
3
+ A TypeScript tool that imports transactions from Lunch Flow to Actual Budget with an intuitive terminal UI for configuration and account mapping.
4
+
5
+ ## Features
6
+
7
+ - 🔗 **Easy Setup**: Simple configuration process for both Lunch Flow and Actual Budget connections
8
+ - 📋 **Account Mapping**: Interactive terminal UI to map Lunch Flow accounts to Actual Budget accounts
9
+ - 📊 **Transaction Import**: Import transactions with proper mapping and deduplication
10
+ - 🎯 **Date Range Selection**: Choose specific date ranges for transaction import
11
+ - 🔍 **Connection Testing**: Test and verify connections to both services
12
+ - 📱 **Terminal UI**: Beautiful, interactive command-line interface
13
+ - 🔄 **Deduplication**: Prevents importing duplicate transactions
14
+
15
+ ## Installation
16
+
17
+ ### Prerequisites
18
+
19
+ - Node.js 16+
20
+ - TypeScript
21
+ - Lunch Flow API access
22
+ - Actual Budget instance
23
+
24
+ ### Quick Install
25
+
26
+ ```bash
27
+ # Clone or download this repository
28
+ git clone <repository-url>
29
+ cd lunch-flow-actual-importer
30
+
31
+ # Install dependencies
32
+ pnpm install
33
+
34
+ # Build the project
35
+ pnpm run build
36
+
37
+ # Run the importer
38
+ pnpm start
39
+ ```
40
+
41
+ ### Development Mode
42
+
43
+ ```bash
44
+ # Run in development mode (no build required)
45
+ pnpm run dev
46
+ ```
47
+
48
+ ## Configuration
49
+
50
+ The tool will guide you through the initial setup process:
51
+
52
+ 1. **Lunch Flow API Key**: Enter your Lunch Flow API key
53
+ 2. **Lunch Flow Base URL**: Enter the API base URL (default: https://api.lunchflow.com)
54
+ 3. **Actual Budget Server URL**: Enter your Actual Budget server URL (default: http://localhost:5007)
55
+ 4. **Actual Budget Budget Sync ID**: Enter your budget sync ID
56
+ 5. **Actual Budget Password**: Enter password if required
57
+
58
+ Configuration is saved to `config.json` in the project directory.
59
+
60
+ ## Usage
61
+
62
+ ### Main Menu
63
+
64
+ The tool provides an interactive menu with the following options:
65
+
66
+ - **🔗 Test connections**: Verify connections to both Lunch Flow and Actual Budget
67
+ - **📋 List available budgets**: Show all budgets available on your Actual Budget server
68
+ - **📋 Configure account mappings**: Map Lunch Flow accounts to Actual Budget accounts
69
+ - **📊 Show current mappings**: Display currently configured account mappings
70
+ - **📥 Import transactions**: Import transactions for a selected date range
71
+ - **⚙️ Reconfigure credentials**: Update API credentials
72
+ - **❌ Exit**: Exit the application
73
+
74
+ ### Account Mapping
75
+
76
+ When configuring account mappings, you'll see:
77
+
78
+ 1. All available Lunch Flow accounts
79
+ 2. All available Actual Budget accounts
80
+ 3. Interactive selection to map each Lunch Flow account to an Actual Budget account
81
+ 4. Option to skip accounts that don't need mapping
82
+
83
+ ### Transaction Import
84
+
85
+ 1. Select a date range for import
86
+ 2. Review a preview of transactions to be imported
87
+ 3. Confirm the import
88
+ 4. Monitor progress with real-time feedback
89
+ 5. Automatic deduplication prevents importing existing transactions
90
+
91
+ ## API Requirements
92
+
93
+ ### Lunch Flow API
94
+
95
+ The tool expects the following Lunch Flow API endpoints:
96
+
97
+ - `GET /accounts` - List accounts
98
+ - `GET /transactions` - List transactions with optional filters
99
+
100
+ Expected response format:
101
+ ```json
102
+ {
103
+ "accounts": [
104
+ {
105
+ "id": "account_id",
106
+ "name": "Account Name",
107
+ "type": "checking",
108
+ "balance": 1000.00,
109
+ "currency": "USD"
110
+ }
111
+ ]
112
+ }
113
+ ```
114
+
115
+ ### Actual Budget API
116
+
117
+ Uses the official `@actual-app/api` package for Actual Budget integration. The API works by:
118
+
119
+ 1. **Connecting to your Actual Budget server** - Downloads budget data to local cache
120
+ 2. **Using proper amount conversion** - Converts between decimal amounts (123.45) and integer format (12345) that Actual uses internally
121
+ 3. **Batch transaction import** - Uses `addTransactions` for efficient bulk imports
122
+ 4. **Local data caching** - Stores budget data in `./actual-data/` directory for faster access
123
+
124
+ For more details, see the [Actual Budget API documentation](https://actualbudget.org/docs/api/).
125
+
126
+ ## Development
127
+
128
+ ### Project Structure
129
+
130
+ ```
131
+ src/
132
+ ├── index.ts # Main entry point
133
+ ├── importer.ts # Main importer class
134
+ ├── lunch-flow-client.ts # Lunch Flow API client
135
+ ├── actual-budget-client.ts # Actual Budget API client
136
+ ├── transaction-mapper.ts # Transaction mapping logic
137
+ ├── config-manager.ts # Configuration management
138
+ ├── ui.ts # Terminal UI components
139
+ └── types.ts # TypeScript type definitions
140
+ ```
141
+
142
+ ### Building
143
+
144
+ ```bash
145
+ # Development mode
146
+ pnpm run dev
147
+
148
+ # Build for production
149
+ pnpm run build
150
+
151
+ # Run built version
152
+ pnpm start
153
+
154
+ # Clean build artifacts
155
+ pnpm run clean
156
+ ```
157
+
158
+ ## Troubleshooting
159
+
160
+ ### Common Issues
161
+
162
+ 1. **Connection Failed**: Verify your API credentials and network connectivity
163
+ 2. **No Accounts Found**: Ensure your Lunch Flow account has accounts and your Actual Budget budget is properly set up
164
+ 3. **Import Failed**: Check that account mappings are configured and transactions are within the selected date range
165
+
166
+ ### Debug Mode
167
+
168
+ Run with debug logging:
169
+
170
+ ```bash
171
+ DEBUG=* pnpm start
172
+ ```
173
+
174
+ ## Contributing
175
+
176
+ 1. Fork the repository
177
+ 2. Create a feature branch
178
+ 3. Make your changes
179
+ 4. Add tests if applicable
180
+ 5. Submit a pull request
181
+
182
+ ## License
183
+
184
+ MIT License - see LICENSE file for details.
185
+
186
+ ## Support
187
+
188
+ For issues and questions:
189
+
190
+ 1. Check the troubleshooting section
191
+ 2. Review the GitHub issues
192
+ 3. Create a new issue with detailed information
193
+
194
+ ---
195
+
196
+ Made with ❤️ for the Actual Budget community
@@ -0,0 +1,19 @@
1
+ import { ActualBudgetTransaction, ActualBudgetAccount } from './types';
2
+ export declare class ActualBudgetClient {
3
+ private serverUrl;
4
+ private budgetSyncId;
5
+ private password?;
6
+ private connected;
7
+ private dataDir;
8
+ constructor(serverUrl: string, budgetSyncId: string, password?: string);
9
+ connect(): Promise<boolean>;
10
+ testConnection(): Promise<boolean>;
11
+ getAccounts(): Promise<ActualBudgetAccount[]>;
12
+ importTransactions(transactions: ActualBudgetTransaction[]): Promise<void>;
13
+ listAvailableBudgets(): Promise<{
14
+ id: string;
15
+ name: string;
16
+ }[]>;
17
+ shutdown(): Promise<void>;
18
+ }
19
+ //# sourceMappingURL=actual-budget-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actual-budget-client.d.ts","sourceRoot":"","sources":["../src/actual-budget-client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC;AAIvE,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,OAAO,CAAS;gBAEZ,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM;IAOhE,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAoC3B,cAAc,IAAI,OAAO,CAAC,OAAO,CAAC;IAUlC,WAAW,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAoB7C,kBAAkB,CAAC,YAAY,EAAE,uBAAuB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAc1E,oBAAoB,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAwB/D,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAQhC"}
@@ -0,0 +1,161 @@
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.ActualBudgetClient = void 0;
40
+ const actualAPI = __importStar(require("@actual-app/api"));
41
+ const fs_1 = __importDefault(require("fs"));
42
+ class ActualBudgetClient {
43
+ constructor(serverUrl, budgetSyncId, password) {
44
+ this.connected = false;
45
+ this.serverUrl = serverUrl;
46
+ this.budgetSyncId = budgetSyncId;
47
+ this.password = password;
48
+ this.dataDir = './actual-data'; // Local cache directory
49
+ }
50
+ async connect() {
51
+ try {
52
+ // Ensure data directory exists
53
+ if (!fs_1.default.existsSync(this.dataDir)) {
54
+ fs_1.default.mkdirSync(this.dataDir, { recursive: true });
55
+ }
56
+ await actualAPI.init({
57
+ dataDir: this.dataDir,
58
+ serverURL: this.serverUrl,
59
+ password: this.password,
60
+ });
61
+ // Download the budget to local cache
62
+ await actualAPI.downloadBudget(this.budgetSyncId);
63
+ this.connected = true;
64
+ return true;
65
+ }
66
+ catch (error) {
67
+ if (error.message.includes('budget directory does not exist')) {
68
+ console.error(`Budget with sync ID "${this.budgetSyncId}" does not exist on the server.`);
69
+ console.error('Please check your budget sync ID and try again.');
70
+ }
71
+ else if (error.message.includes('ECONNREFUSED')) {
72
+ console.error(`Cannot connect to Actual Budget server at ${this.serverUrl}`);
73
+ console.error('Please check that your Actual Budget server is running.');
74
+ }
75
+ else if (error.message.includes('not found')) {
76
+ console.error(`Budget with sync ID "${this.budgetSyncId}" not found on the server.`);
77
+ console.error('Please verify your budget sync ID in Actual Budget settings.');
78
+ }
79
+ else {
80
+ console.error('Failed to connect to Actual Budget:', error.message);
81
+ }
82
+ this.connected = false;
83
+ return false;
84
+ }
85
+ }
86
+ async testConnection() {
87
+ try {
88
+ await this.connect();
89
+ return this.connected;
90
+ }
91
+ catch (error) {
92
+ console.error('Actual Budget connection test failed:', error.message);
93
+ return false;
94
+ }
95
+ }
96
+ async getAccounts() {
97
+ if (!this.connected) {
98
+ await this.connect();
99
+ }
100
+ try {
101
+ const accounts = await actualAPI.getAccounts();
102
+ return accounts.map((account) => ({
103
+ id: account.id,
104
+ name: account.name,
105
+ type: account.type,
106
+ balance: actualAPI.utils.integerToAmount(account.balance || 0),
107
+ currency: 'USD', // Assuming USD, adjust as needed
108
+ }));
109
+ }
110
+ catch (error) {
111
+ console.error('Failed to fetch Actual Budget accounts:', error.message);
112
+ throw new Error(`Failed to fetch accounts: ${error.message}`);
113
+ }
114
+ }
115
+ async importTransactions(transactions) {
116
+ if (!this.connected) {
117
+ await this.connect();
118
+ }
119
+ try {
120
+ const result = await actualAPI.importTransactions(transactions[0].account, transactions);
121
+ console.log('Transactions imported successfully:', result);
122
+ }
123
+ catch (error) {
124
+ console.error('Failed to import transactions to Actual Budget:', error.message);
125
+ throw new Error(`Failed to import transactions: ${error.message}`);
126
+ }
127
+ }
128
+ async listAvailableBudgets() {
129
+ try {
130
+ // Ensure data directory exists
131
+ if (!fs_1.default.existsSync(this.dataDir)) {
132
+ fs_1.default.mkdirSync(this.dataDir, { recursive: true });
133
+ }
134
+ await actualAPI.init({
135
+ dataDir: this.dataDir,
136
+ serverURL: this.serverUrl,
137
+ password: this.password,
138
+ });
139
+ const budgets = await actualAPI.getBudgets();
140
+ return budgets.map((budget) => ({
141
+ id: budget.id,
142
+ name: budget.name || 'Unnamed Budget'
143
+ }));
144
+ }
145
+ catch (error) {
146
+ console.error('Failed to fetch available budgets:', error.message);
147
+ throw new Error(`Failed to fetch budgets: ${error.message}`);
148
+ }
149
+ }
150
+ async shutdown() {
151
+ try {
152
+ await actualAPI.shutdown();
153
+ this.connected = false;
154
+ }
155
+ catch (error) {
156
+ console.error('Error during shutdown:', error.message);
157
+ }
158
+ }
159
+ }
160
+ exports.ActualBudgetClient = ActualBudgetClient;
161
+ //# sourceMappingURL=actual-budget-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"actual-budget-client.js","sourceRoot":"","sources":["../src/actual-budget-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Config, AccountMapping } from './types';
2
+ export declare class ConfigManager {
3
+ private configPath;
4
+ constructor(configPath?: string);
5
+ loadConfig(): Config | null;
6
+ saveConfig(config: Config): void;
7
+ createDefaultConfig(): Config;
8
+ updateLunchFlowConfig(apiKey: string, baseUrl: string): void;
9
+ updateActualBudgetConfig(serverUrl: string, budgetSyncId: string, password?: string): void;
10
+ updateAccountMappings(mappings: AccountMapping[]): void;
11
+ private validateConfig;
12
+ getConfigPath(): string;
13
+ isConfigured(): boolean;
14
+ getSafeConfig(): Partial<Config> | null;
15
+ }
16
+ //# sourceMappingURL=config-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-manager.d.ts","sourceRoot":"","sources":["../src/config-manager.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEjD,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAS;gBAEf,UAAU,GAAE,MAAgD;IAIxE,UAAU,IAAI,MAAM,GAAG,IAAI;IAqB3B,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAehC,mBAAmB,IAAI,MAAM;IAe7B,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAM5D,wBAAwB,CAAC,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI;IAM1F,qBAAqB,CAAC,QAAQ,EAAE,cAAc,EAAE,GAAG,IAAI;IAQvD,OAAO,CAAC,cAAc;IActB,aAAa,IAAI,MAAM;IAKvB,YAAY,IAAI,OAAO;IASvB,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI;CAiBxC"}
@@ -0,0 +1,119 @@
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.ConfigManager = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ class ConfigManager {
10
+ constructor(configPath = path_1.default.join(process.cwd(), 'config.json')) {
11
+ this.configPath = configPath;
12
+ }
13
+ loadConfig() {
14
+ try {
15
+ if (!fs_1.default.existsSync(this.configPath)) {
16
+ return null;
17
+ }
18
+ const configData = fs_1.default.readFileSync(this.configPath, 'utf8');
19
+ const config = JSON.parse(configData);
20
+ // Validate config structure
21
+ if (!this.validateConfig(config)) {
22
+ console.warn('Invalid config file structure, will recreate');
23
+ return null;
24
+ }
25
+ return config;
26
+ }
27
+ catch (error) {
28
+ console.error('Failed to load config:', error);
29
+ return null;
30
+ }
31
+ }
32
+ saveConfig(config) {
33
+ try {
34
+ // Ensure directory exists
35
+ const dir = path_1.default.dirname(this.configPath);
36
+ if (!fs_1.default.existsSync(dir)) {
37
+ fs_1.default.mkdirSync(dir, { recursive: true });
38
+ }
39
+ fs_1.default.writeFileSync(this.configPath, JSON.stringify(config, null, 2));
40
+ }
41
+ catch (error) {
42
+ console.error('Failed to save config:', error);
43
+ throw error;
44
+ }
45
+ }
46
+ createDefaultConfig() {
47
+ return {
48
+ lunchFlow: {
49
+ apiKey: '',
50
+ baseUrl: 'https://api.lunchflow.com',
51
+ },
52
+ actualBudget: {
53
+ serverUrl: '',
54
+ budgetSyncId: '',
55
+ password: '',
56
+ },
57
+ accountMappings: [],
58
+ };
59
+ }
60
+ updateLunchFlowConfig(apiKey, baseUrl) {
61
+ const config = this.loadConfig() || this.createDefaultConfig();
62
+ config.lunchFlow = { apiKey, baseUrl };
63
+ this.saveConfig(config);
64
+ }
65
+ updateActualBudgetConfig(serverUrl, budgetSyncId, password) {
66
+ const config = this.loadConfig() || this.createDefaultConfig();
67
+ config.actualBudget = { serverUrl, budgetSyncId, password };
68
+ this.saveConfig(config);
69
+ }
70
+ updateAccountMappings(mappings) {
71
+ const config = this.loadConfig();
72
+ if (config) {
73
+ config.accountMappings = mappings;
74
+ this.saveConfig(config);
75
+ }
76
+ }
77
+ validateConfig(config) {
78
+ return (config &&
79
+ typeof config === 'object' &&
80
+ config.lunchFlow &&
81
+ typeof config.lunchFlow.apiKey === 'string' &&
82
+ typeof config.lunchFlow.baseUrl === 'string' &&
83
+ config.actualBudget &&
84
+ typeof config.actualBudget.serverUrl === 'string' &&
85
+ typeof config.actualBudget.budgetSyncId === 'string' &&
86
+ Array.isArray(config.accountMappings));
87
+ }
88
+ getConfigPath() {
89
+ return this.configPath;
90
+ }
91
+ // Check if config exists and has required fields
92
+ isConfigured() {
93
+ const config = this.loadConfig();
94
+ return config !== null &&
95
+ config.lunchFlow.apiKey.length > 0 &&
96
+ config.actualBudget.serverUrl.length > 0 &&
97
+ config.actualBudget.budgetSyncId.length > 0;
98
+ }
99
+ // Get a safe version of config for display (hides sensitive data)
100
+ getSafeConfig() {
101
+ const config = this.loadConfig();
102
+ if (!config)
103
+ return null;
104
+ return {
105
+ lunchFlow: {
106
+ apiKey: config.lunchFlow.apiKey ? '***' + config.lunchFlow.apiKey.slice(-4) : '',
107
+ baseUrl: config.lunchFlow.baseUrl,
108
+ },
109
+ actualBudget: {
110
+ serverUrl: config.actualBudget.serverUrl,
111
+ budgetSyncId: config.actualBudget.budgetSyncId,
112
+ password: config.actualBudget.password ? '***' : '',
113
+ },
114
+ accountMappings: config.accountMappings,
115
+ };
116
+ }
117
+ }
118
+ exports.ConfigManager = ConfigManager;
119
+ //# sourceMappingURL=config-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-manager.js","sourceRoot":"","sources":["../src/config-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { ConnectionStatus } from './types';
2
+ export declare class LunchFlowImporter {
3
+ private lfClient;
4
+ private abClient;
5
+ private configManager;
6
+ private ui;
7
+ private config;
8
+ constructor();
9
+ initialize(): Promise<void>;
10
+ private setupConfiguration;
11
+ private updateClients;
12
+ testConnections(): Promise<ConnectionStatus>;
13
+ configureAccountMappings(): Promise<void>;
14
+ importTransactions(): Promise<void>;
15
+ showCurrentMappings(): Promise<void>;
16
+ listAvailableBudgets(): Promise<void>;
17
+ reconfigureCredentials(): Promise<void>;
18
+ run(): Promise<void>;
19
+ }
20
+ //# sourceMappingURL=importer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"importer.d.ts","sourceRoot":"","sources":["../src/importer.ts"],"names":[],"mappings":"AAKA,OAAO,EAA0B,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAInE,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,QAAQ,CAAqB;IACrC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,EAAE,CAAa;IACvB,OAAO,CAAC,MAAM,CAAuB;;IAY/B,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAYnB,kBAAkB;IAehC,OAAO,CAAC,aAAa;IAcf,eAAe,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAoB5C,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAsCzC,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAyDnC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQpC,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC;IAkCrC,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAsBvC,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;CAgC3B"}