newo 1.5.0 → 1.5.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/dist/cli.js CHANGED
@@ -4,53 +4,234 @@ import dotenv from 'dotenv';
4
4
  import { makeClient, getProjectMeta, importAkbArticle } from './api.js';
5
5
  import { pullAll, pushChanged, status } from './sync.js';
6
6
  import { parseAkbFile, prepareArticlesForImport } from './akb.js';
7
+ import { initializeEnvironment, ENV, EnvValidationError } from './env.js';
8
+ import { parseCustomerConfigAsync, listCustomers, getCustomer, getDefaultCustomer, validateCustomerConfig } from './customerAsync.js';
9
+ import { getValidAccessToken } from './auth.js';
7
10
  import path from 'path';
11
+ // Enhanced error logging for CLI
12
+ function logCliError(level, message, meta) {
13
+ const timestamp = new Date().toISOString();
14
+ const logEntry = {
15
+ timestamp,
16
+ level,
17
+ module: 'cli',
18
+ message,
19
+ ...meta
20
+ };
21
+ // Only log JSON format in verbose mode, otherwise use clean user messages
22
+ const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
23
+ if (verbose) {
24
+ if (level === 'error') {
25
+ console.error(JSON.stringify(logEntry));
26
+ }
27
+ else if (level === 'warn') {
28
+ console.warn(JSON.stringify(logEntry));
29
+ }
30
+ else {
31
+ console.log(JSON.stringify(logEntry));
32
+ }
33
+ }
34
+ else {
35
+ // Clean user-facing messages
36
+ if (level === 'error') {
37
+ console.error(`❌ ${message}`);
38
+ }
39
+ else if (level === 'warn') {
40
+ console.warn(`⚠️ ${message}`);
41
+ }
42
+ else {
43
+ console.log(`ℹ️ ${message}`);
44
+ }
45
+ }
46
+ }
47
+ // Enhanced error handling with user-friendly messages
48
+ function handleCliError(error, operation = 'operation') {
49
+ const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
50
+ if (error instanceof Error) {
51
+ // Authentication errors
52
+ if (error.message.includes('API key') || error.message.includes('Authentication failed')) {
53
+ logCliError('error', 'Authentication failed. Please check your API key configuration.');
54
+ if (!verbose) {
55
+ console.error('\n💡 Troubleshooting tips:');
56
+ console.error(' • Verify your API key is correct in .env file');
57
+ console.error(' • For multi-customer setup, check NEWO_CUSTOMER_<IDN>_API_KEY');
58
+ console.error(' • Run with --verbose for detailed error information');
59
+ }
60
+ }
61
+ // Network errors
62
+ else if (error.message.includes('Network timeout') || error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) {
63
+ logCliError('error', 'Network connection failed. Please check your internet connection.');
64
+ if (!verbose) {
65
+ console.error('\n💡 Troubleshooting tips:');
66
+ console.error(' • Check your internet connection');
67
+ console.error(' • Verify NEWO_BASE_URL is correct');
68
+ console.error(' • Try again in a few moments');
69
+ }
70
+ }
71
+ // Environment configuration errors
72
+ else if (error instanceof EnvValidationError || error.message.includes('not set')) {
73
+ logCliError('error', 'Configuration error. Please check your environment setup.');
74
+ if (!verbose) {
75
+ console.error('\n💡 Setup help:');
76
+ console.error(' • Copy .env.example to .env and configure your settings');
77
+ console.error(' • Run "newo --help" to see configuration examples');
78
+ console.error(' • Check the README for detailed setup instructions');
79
+ }
80
+ }
81
+ // File system errors
82
+ else if (error.message.includes('ENOENT') || error.message.includes('EACCES')) {
83
+ logCliError('error', 'File system error. Please check file permissions and paths.');
84
+ }
85
+ // Rate limiting
86
+ else if (error.message.includes('Rate limit exceeded')) {
87
+ logCliError('error', 'Rate limit exceeded. Please wait before trying again.');
88
+ }
89
+ // General API errors
90
+ else if (error.message.includes('response') || error.message.includes('status')) {
91
+ logCliError('error', `API error during ${operation}. Please try again or contact support.`);
92
+ }
93
+ // Unknown errors
94
+ else {
95
+ logCliError('error', `Unexpected error during ${operation}: ${error.message}`);
96
+ if (!verbose) {
97
+ console.error('\n💡 For more details, run the command with --verbose flag');
98
+ }
99
+ }
100
+ if (verbose) {
101
+ logCliError('error', 'Full error details', {
102
+ operation,
103
+ errorType: error.constructor.name,
104
+ stack: error.stack?.split('\n').slice(0, 5).join('\n') // First 5 lines of stack
105
+ });
106
+ }
107
+ }
108
+ else {
109
+ logCliError('error', `Unknown error during ${operation}: ${String(error)}`);
110
+ }
111
+ process.exit(1);
112
+ }
8
113
  dotenv.config();
9
- const { NEWO_PROJECT_ID } = process.env;
10
114
  async function main() {
115
+ try {
116
+ // Initialize and validate environment at startup
117
+ initializeEnvironment();
118
+ }
119
+ catch (error) {
120
+ if (error instanceof EnvValidationError) {
121
+ console.error('Environment validation failed:', error.message);
122
+ process.exit(1);
123
+ }
124
+ throw error;
125
+ }
11
126
  const args = minimist(process.argv.slice(2));
12
127
  const cmd = args._[0];
13
128
  const verbose = Boolean(args.verbose || args.v);
129
+ // Parse customer configuration (async for API key array support)
130
+ let customerConfig;
131
+ try {
132
+ customerConfig = await parseCustomerConfigAsync(ENV, verbose);
133
+ validateCustomerConfig(customerConfig);
134
+ }
135
+ catch (error) {
136
+ logCliError('error', 'Failed to parse customer configuration');
137
+ if (error instanceof Error) {
138
+ logCliError('error', error.message);
139
+ }
140
+ process.exit(1);
141
+ }
142
+ // Handle customer selection
143
+ let selectedCustomer;
144
+ if (cmd === 'list-customers') {
145
+ const customers = listCustomers(customerConfig);
146
+ console.log('Available customers:');
147
+ for (const customerIdn of customers) {
148
+ const isDefault = customerConfig.defaultCustomer === customerIdn;
149
+ console.log(` ${customerIdn}${isDefault ? ' (default)' : ''}`);
150
+ }
151
+ return;
152
+ }
153
+ if (args.customer) {
154
+ const customer = getCustomer(customerConfig, args.customer);
155
+ if (!customer) {
156
+ console.error(`Unknown customer: ${args.customer}`);
157
+ console.error(`Available customers: ${listCustomers(customerConfig).join(', ')}`);
158
+ process.exit(1);
159
+ }
160
+ selectedCustomer = customer;
161
+ }
162
+ else {
163
+ try {
164
+ selectedCustomer = getDefaultCustomer(customerConfig);
165
+ }
166
+ catch (error) {
167
+ const message = error instanceof Error ? error.message : String(error);
168
+ console.error(message);
169
+ process.exit(1);
170
+ }
171
+ }
14
172
  if (!cmd || ['help', '-h', '--help'].includes(cmd)) {
15
- console.log(`NEWO CLI
173
+ console.log(`NEWO CLI - Multi-Customer Support
16
174
  Usage:
17
- newo pull # download all projects -> ./projects/ OR specific project if NEWO_PROJECT_ID set
18
- newo push # upload modified *.guidance/*.jinja back to NEWO
19
- newo status # show modified files
20
- newo meta # get project metadata (debug, requires NEWO_PROJECT_ID)
21
- newo import-akb <file> <persona_id> # import AKB articles from file
175
+ newo pull [--customer <idn>] # download projects -> ./newo_customers/<idn>/projects/
176
+ newo push [--customer <idn>] # upload modified *.guidance/*.jinja back to NEWO
177
+ newo status [--customer <idn>] # show modified files
178
+ newo list-customers # list available customers
179
+ newo meta [--customer <idn>] # get project metadata (debug)
180
+ newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles from file
22
181
 
23
182
  Flags:
183
+ --customer <idn> # specify customer (if not set, uses default)
24
184
  --verbose, -v # enable detailed logging
25
185
 
26
- Env:
27
- NEWO_BASE_URL, NEWO_PROJECT_ID (optional), NEWO_API_KEY, NEWO_REFRESH_URL (optional)
186
+ Environment Variables:
187
+ NEWO_BASE_URL # NEWO API base URL (default: https://app.newo.ai)
188
+ NEWO_CUSTOMER_<IDN>_API_KEY # API key for customer <IDN>
189
+ NEWO_CUSTOMER_<IDN>_PROJECT_ID # Optional: specific project ID for customer
190
+ NEWO_DEFAULT_CUSTOMER # Optional: default customer to use
28
191
 
29
- Notes:
30
- - multi-project support: pull downloads all accessible projects or single project based on NEWO_PROJECT_ID
31
- - If NEWO_PROJECT_ID is set, pull downloads only that project
32
- - If NEWO_PROJECT_ID is not set, pull downloads all projects accessible with your API key
33
- - Projects are stored in ./projects/{project-idn}/ folders
34
- - Each project folder contains metadata.json and flows.yaml
192
+ Multi-Customer Examples:
193
+ # Configure customers in .env:
194
+ NEWO_CUSTOMER_acme_API_KEY=your_acme_api_key
195
+ NEWO_CUSTOMER_globex_API_KEY=your_globex_api_key
196
+ NEWO_DEFAULT_CUSTOMER=acme
197
+
198
+ # Commands:
199
+ newo pull --customer acme # Pull projects for Acme
200
+ newo push --customer globex # Push changes for Globex
201
+ newo status # Status for default customer
202
+
203
+ File Structure:
204
+ newo_customers/
205
+ ├── acme/
206
+ │ └── projects/
207
+ │ └── project1/
208
+ └── globex/
209
+ └── projects/
210
+ └── project2/
35
211
  `);
36
212
  return;
37
213
  }
38
- const client = await makeClient(verbose);
214
+ // Get access token for the selected customer
215
+ const accessToken = await getValidAccessToken(selectedCustomer);
216
+ const client = await makeClient(verbose, accessToken);
39
217
  if (cmd === 'pull') {
40
- // If PROJECT_ID is set, pull single project; otherwise pull all projects
41
- await pullAll(client, NEWO_PROJECT_ID || null, verbose);
218
+ // Use customer-specific project ID if set, otherwise pull all projects
219
+ const projectId = selectedCustomer.projectId || null;
220
+ await pullAll(client, selectedCustomer, projectId, verbose);
42
221
  }
43
222
  else if (cmd === 'push') {
44
- await pushChanged(client, verbose);
223
+ await pushChanged(client, selectedCustomer, verbose);
45
224
  }
46
225
  else if (cmd === 'status') {
47
- await status(verbose);
226
+ await status(selectedCustomer, verbose);
48
227
  }
49
228
  else if (cmd === 'meta') {
50
- if (!NEWO_PROJECT_ID) {
51
- throw new Error('NEWO_PROJECT_ID is not set in env');
229
+ if (!selectedCustomer.projectId) {
230
+ console.error(`No project ID configured for customer ${selectedCustomer.idn}`);
231
+ console.error(`Set NEWO_CUSTOMER_${selectedCustomer.idn.toUpperCase()}_PROJECT_ID in your .env file`);
232
+ process.exit(1);
52
233
  }
53
- const meta = await getProjectMeta(client, NEWO_PROJECT_ID);
234
+ const meta = await getProjectMeta(client, selectedCustomer.projectId);
54
235
  console.log(JSON.stringify(meta, null, 2));
55
236
  }
56
237
  else if (cmd === 'import-akb') {
@@ -65,7 +246,7 @@ Notes:
65
246
  try {
66
247
  if (verbose)
67
248
  console.log(`📖 Parsing AKB file: ${filePath}`);
68
- const articles = parseAkbFile(filePath);
249
+ const articles = await parseAkbFile(filePath);
69
250
  console.log(`✓ Parsed ${articles.length} articles from ${akbFile}`);
70
251
  if (verbose)
71
252
  console.log(`🔧 Preparing articles for persona: ${personaId}`);
@@ -85,7 +266,11 @@ Notes:
85
266
  }
86
267
  catch (error) {
87
268
  errorCount++;
88
- const errorMessage = error?.response?.data || error.message;
269
+ const errorMessage = error instanceof Error && 'response' in error
270
+ ? error?.response?.data
271
+ : error instanceof Error
272
+ ? error.message
273
+ : String(error);
89
274
  console.error(`\n❌ Failed to import ${article.topic_name}:`, errorMessage);
90
275
  }
91
276
  }
@@ -94,7 +279,8 @@ Notes:
94
279
  console.log(`✅ Import complete: ${successCount} successful, ${errorCount} failed`);
95
280
  }
96
281
  catch (error) {
97
- console.error('❌ AKB import failed:', error.message);
282
+ const message = error instanceof Error ? error.message : String(error);
283
+ console.error('❌ AKB import failed:', message);
98
284
  process.exit(1);
99
285
  }
100
286
  }
@@ -104,8 +290,18 @@ Notes:
104
290
  }
105
291
  }
106
292
  main().catch((error) => {
107
- const errorData = 'response' in error ? error?.response?.data : error;
108
- console.error(errorData || error);
109
- process.exit(1);
293
+ // Determine operation context from command line args
294
+ const args = process.argv.slice(2);
295
+ const cmd = args.find(arg => !arg.startsWith('-')) || 'unknown command';
296
+ // Handle API errors with specific data
297
+ if (error instanceof Error && 'response' in error) {
298
+ const apiError = error;
299
+ const responseData = apiError.response?.data;
300
+ const status = apiError.response?.status;
301
+ if (responseData && status) {
302
+ logCliError('error', `API error (${status}): ${JSON.stringify(responseData)}`);
303
+ }
304
+ }
305
+ handleCliError(error, cmd);
110
306
  });
111
307
  //# sourceMappingURL=cli.js.map
@@ -0,0 +1,23 @@
1
+ import type { NewoEnvironment, CustomerConfig, MultiCustomerConfig } from './types.js';
2
+ /**
3
+ * Parse environment variables to extract customer configurations
4
+ * Supports both array-based (NEWO_API_KEYS) and individual customer configs
5
+ */
6
+ export declare function parseCustomerConfig(env: NewoEnvironment): MultiCustomerConfig;
7
+ /**
8
+ * List all available customer IDNs
9
+ */
10
+ export declare function listCustomers(config: MultiCustomerConfig): string[];
11
+ /**
12
+ * Get customer configuration by IDN
13
+ */
14
+ export declare function getCustomer(config: MultiCustomerConfig, customerIdn: string): CustomerConfig | null;
15
+ /**
16
+ * Get default customer or throw error if none
17
+ */
18
+ export declare function getDefaultCustomer(config: MultiCustomerConfig): CustomerConfig;
19
+ /**
20
+ * Validate customer configuration
21
+ */
22
+ export declare function validateCustomerConfig(config: MultiCustomerConfig): void;
23
+ //# sourceMappingURL=customer.d.ts.map
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Parse environment variables to extract customer configurations
3
+ * Supports both array-based (NEWO_API_KEYS) and individual customer configs
4
+ */
5
+ export function parseCustomerConfig(env) {
6
+ const customers = {};
7
+ // Parse customer-specific API keys
8
+ // Format: NEWO_CUSTOMER_[IDN]_API_KEY=api_key
9
+ for (const [key, value] of Object.entries(env)) {
10
+ if (key.startsWith('NEWO_CUSTOMER_') && key.endsWith('_API_KEY') && value) {
11
+ const idn = key.slice('NEWO_CUSTOMER_'.length, -'_API_KEY'.length).toLowerCase();
12
+ if (!customers[idn]) {
13
+ customers[idn] = { idn, apiKey: value };
14
+ }
15
+ else {
16
+ customers[idn].apiKey = value;
17
+ }
18
+ // Check for corresponding project ID
19
+ const projectIdKey = `NEWO_CUSTOMER_${idn.toUpperCase()}_PROJECT_ID`;
20
+ if (env[projectIdKey]) {
21
+ customers[idn].projectId = env[projectIdKey];
22
+ }
23
+ }
24
+ }
25
+ // Check for legacy single customer mode
26
+ if (env.NEWO_API_KEY && Object.keys(customers).length === 0) {
27
+ customers['default'] = {
28
+ idn: 'default',
29
+ apiKey: env.NEWO_API_KEY,
30
+ projectId: env.NEWO_PROJECT_ID
31
+ };
32
+ }
33
+ return {
34
+ customers,
35
+ defaultCustomer: env.NEWO_DEFAULT_CUSTOMER || (Object.keys(customers).length === 1 ? Object.keys(customers)[0] : undefined)
36
+ };
37
+ }
38
+ /**
39
+ * List all available customer IDNs
40
+ */
41
+ export function listCustomers(config) {
42
+ return Object.keys(config.customers).sort();
43
+ }
44
+ /**
45
+ * Get customer configuration by IDN
46
+ */
47
+ export function getCustomer(config, customerIdn) {
48
+ return config.customers[customerIdn] || null;
49
+ }
50
+ /**
51
+ * Get default customer or throw error if none
52
+ */
53
+ export function getDefaultCustomer(config) {
54
+ if (config.defaultCustomer) {
55
+ const customer = getCustomer(config, config.defaultCustomer);
56
+ if (customer)
57
+ return customer;
58
+ }
59
+ const customerIdns = listCustomers(config);
60
+ if (customerIdns.length === 1) {
61
+ const firstCustomerIdn = customerIdns[0];
62
+ if (firstCustomerIdn) {
63
+ return config.customers[firstCustomerIdn];
64
+ }
65
+ }
66
+ if (customerIdns.length === 0) {
67
+ throw new Error('No customers configured. Please set NEWO_CUSTOMER_[IDN]_API_KEY in your .env file.');
68
+ }
69
+ throw new Error(`Multiple customers configured but no default specified. Available: ${customerIdns.join(', ')}. ` +
70
+ `Set NEWO_DEFAULT_CUSTOMER or use --customer flag.`);
71
+ }
72
+ /**
73
+ * Validate customer configuration
74
+ */
75
+ export function validateCustomerConfig(config) {
76
+ const customers = listCustomers(config);
77
+ if (customers.length === 0) {
78
+ throw new Error('No customers configured. Please set NEWO_CUSTOMER_[IDN]_API_KEY in your .env file.');
79
+ }
80
+ for (const customerIdn of customers) {
81
+ const customer = config.customers[customerIdn];
82
+ if (!customer.apiKey) {
83
+ throw new Error(`Customer ${customerIdn} missing API key`);
84
+ }
85
+ }
86
+ }
87
+ //# sourceMappingURL=customer.js.map
@@ -0,0 +1,22 @@
1
+ import type { NewoEnvironment, CustomerConfig, MultiCustomerConfig } from './types.js';
2
+ /**
3
+ * Async version of customer configuration parsing that supports API key array initialization
4
+ */
5
+ export declare function parseCustomerConfigAsync(env: NewoEnvironment, verbose?: boolean): Promise<MultiCustomerConfig>;
6
+ /**
7
+ * List all available customer IDNs
8
+ */
9
+ export declare function listCustomers(config: MultiCustomerConfig): string[];
10
+ /**
11
+ * Get customer configuration by IDN
12
+ */
13
+ export declare function getCustomer(config: MultiCustomerConfig, customerIdn: string): CustomerConfig | null;
14
+ /**
15
+ * Get default customer or throw error if none
16
+ */
17
+ export declare function getDefaultCustomer(config: MultiCustomerConfig): CustomerConfig;
18
+ /**
19
+ * Validate customer configuration
20
+ */
21
+ export declare function validateCustomerConfig(config: MultiCustomerConfig): void;
22
+ //# sourceMappingURL=customerAsync.d.ts.map
@@ -0,0 +1,67 @@
1
+ import { initializeCustomersFromApiKeys, usesArrayBasedConfig } from './customerInit.js';
2
+ import { parseCustomerConfig } from './customer.js';
3
+ /**
4
+ * Async version of customer configuration parsing that supports API key array initialization
5
+ */
6
+ export async function parseCustomerConfigAsync(env, verbose = false) {
7
+ // If using array-based config, initialize from API keys
8
+ if (usesArrayBasedConfig(env)) {
9
+ if (verbose)
10
+ console.log('📝 Using array-based API key configuration');
11
+ return await initializeCustomersFromApiKeys(env, verbose);
12
+ }
13
+ // Fall back to synchronous individual customer parsing
14
+ if (verbose)
15
+ console.log('📝 Using individual customer configuration');
16
+ return parseCustomerConfig(env);
17
+ }
18
+ /**
19
+ * List all available customer IDNs
20
+ */
21
+ export function listCustomers(config) {
22
+ return Object.keys(config.customers).sort();
23
+ }
24
+ /**
25
+ * Get customer configuration by IDN
26
+ */
27
+ export function getCustomer(config, customerIdn) {
28
+ return config.customers[customerIdn] || null;
29
+ }
30
+ /**
31
+ * Get default customer or throw error if none
32
+ */
33
+ export function getDefaultCustomer(config) {
34
+ if (config.defaultCustomer) {
35
+ const customer = getCustomer(config, config.defaultCustomer);
36
+ if (customer)
37
+ return customer;
38
+ }
39
+ const customerIdns = listCustomers(config);
40
+ if (customerIdns.length === 1) {
41
+ const firstCustomerIdn = customerIdns[0];
42
+ if (firstCustomerIdn) {
43
+ return config.customers[firstCustomerIdn];
44
+ }
45
+ }
46
+ if (customerIdns.length === 0) {
47
+ throw new Error('No customers configured. Please set NEWO_API_KEYS or NEWO_CUSTOMER_[IDN]_API_KEY in your .env file.');
48
+ }
49
+ throw new Error(`Multiple customers configured but no default specified. Available: ${customerIdns.join(', ')}. ` +
50
+ `Set NEWO_DEFAULT_CUSTOMER or use --customer flag.`);
51
+ }
52
+ /**
53
+ * Validate customer configuration
54
+ */
55
+ export function validateCustomerConfig(config) {
56
+ const customers = listCustomers(config);
57
+ if (customers.length === 0) {
58
+ throw new Error('No customers configured. Please set NEWO_API_KEYS or NEWO_CUSTOMER_[IDN]_API_KEY in your .env file.');
59
+ }
60
+ for (const customerIdn of customers) {
61
+ const customer = config.customers[customerIdn];
62
+ if (!customer.apiKey) {
63
+ throw new Error(`Customer ${customerIdn} missing API key`);
64
+ }
65
+ }
66
+ }
67
+ //# sourceMappingURL=customerAsync.js.map
@@ -0,0 +1,10 @@
1
+ import type { NewoEnvironment, MultiCustomerConfig } from './types.js';
2
+ /**
3
+ * Initialize customer configurations from API keys array
4
+ */
5
+ export declare function initializeCustomersFromApiKeys(env: NewoEnvironment, verbose?: boolean): Promise<MultiCustomerConfig>;
6
+ /**
7
+ * Check if environment uses array-based configuration
8
+ */
9
+ export declare function usesArrayBasedConfig(env: NewoEnvironment): boolean;
10
+ //# sourceMappingURL=customerInit.d.ts.map
@@ -0,0 +1,78 @@
1
+ import { getCustomerProfile, makeClient } from './api.js';
2
+ import { exchangeApiKeyForToken } from './auth.js';
3
+ /**
4
+ * Initialize customer configurations from API keys array
5
+ */
6
+ export async function initializeCustomersFromApiKeys(env, verbose = false) {
7
+ if (!env.NEWO_API_KEYS) {
8
+ throw new Error('NEWO_API_KEYS not set. Provide API keys array in .env file.');
9
+ }
10
+ let apiKeyConfigs;
11
+ try {
12
+ apiKeyConfigs = JSON.parse(env.NEWO_API_KEYS);
13
+ }
14
+ catch (error) {
15
+ throw new Error(`Invalid NEWO_API_KEYS format. Must be valid JSON array: ${error}`);
16
+ }
17
+ if (!Array.isArray(apiKeyConfigs)) {
18
+ throw new Error('NEWO_API_KEYS must be an array');
19
+ }
20
+ const customers = {};
21
+ if (verbose)
22
+ console.log(`🔍 Initializing ${apiKeyConfigs.length} API keys...`);
23
+ for (const [index, keyConfig] of apiKeyConfigs.entries()) {
24
+ try {
25
+ // Normalize config
26
+ const apiKey = typeof keyConfig === 'string' ? keyConfig : keyConfig.key;
27
+ const projectId = typeof keyConfig === 'object' ? keyConfig.project_id : undefined;
28
+ if (verbose)
29
+ console.log(` [${index + 1}/${apiKeyConfigs.length}] Exchanging API key for token...`);
30
+ // Create temporary customer config for token exchange
31
+ const tempCustomer = {
32
+ idn: 'temp',
33
+ apiKey,
34
+ projectId
35
+ };
36
+ // Exchange API key for token
37
+ const tokens = await exchangeApiKeyForToken(tempCustomer);
38
+ if (verbose)
39
+ console.log(` [${index + 1}/${apiKeyConfigs.length}] Getting customer profile...`);
40
+ // Create client with token
41
+ const client = await makeClient(verbose, tokens.access_token);
42
+ // Get customer profile to extract IDN
43
+ const profile = await getCustomerProfile(client);
44
+ if (verbose) {
45
+ console.log(` [${index + 1}/${apiKeyConfigs.length}] ✓ Customer: ${profile.idn} (${profile.organization_name})`);
46
+ }
47
+ // Store customer config with real IDN
48
+ customers[profile.idn] = {
49
+ idn: profile.idn,
50
+ apiKey,
51
+ projectId
52
+ };
53
+ }
54
+ catch (error) {
55
+ const message = error instanceof Error ? error.message : String(error);
56
+ console.error(` [${index + 1}/${apiKeyConfigs.length}] ❌ Failed to initialize API key: ${message}`);
57
+ // Continue with other keys rather than failing entirely
58
+ }
59
+ }
60
+ const customerIdns = Object.keys(customers);
61
+ if (customerIdns.length === 0) {
62
+ throw new Error('No valid API keys found. Check your NEWO_API_KEYS configuration.');
63
+ }
64
+ if (verbose) {
65
+ console.log(`✅ Initialized ${customerIdns.length} customers: ${customerIdns.join(', ')}`);
66
+ }
67
+ return {
68
+ customers,
69
+ defaultCustomer: env.NEWO_DEFAULT_CUSTOMER || (customerIdns.length === 1 ? customerIdns[0] : undefined)
70
+ };
71
+ }
72
+ /**
73
+ * Check if environment uses array-based configuration
74
+ */
75
+ export function usesArrayBasedConfig(env) {
76
+ return Boolean(env.NEWO_API_KEYS);
77
+ }
78
+ //# sourceMappingURL=customerInit.js.map
package/dist/env.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Validated environment configuration
3
+ */
4
+ export interface ValidatedEnv {
5
+ readonly NEWO_BASE_URL: string;
6
+ readonly NEWO_PROJECT_ID: string | undefined;
7
+ readonly NEWO_API_KEY: string | undefined;
8
+ readonly NEWO_API_KEYS: string | undefined;
9
+ readonly NEWO_ACCESS_TOKEN: string | undefined;
10
+ readonly NEWO_REFRESH_TOKEN: string | undefined;
11
+ readonly NEWO_REFRESH_URL: string | undefined;
12
+ readonly NEWO_DEFAULT_CUSTOMER: string | undefined;
13
+ readonly [key: string]: string | undefined;
14
+ }
15
+ /**
16
+ * Environment validation errors with clear messaging
17
+ */
18
+ export declare class EnvValidationError extends Error {
19
+ constructor(message: string);
20
+ }
21
+ /**
22
+ * Validates required environment variables and returns typed configuration
23
+ */
24
+ export declare function validateEnvironment(): ValidatedEnv;
25
+ /**
26
+ * Global validated environment - call validateEnvironment() once at startup
27
+ */
28
+ export declare let ENV: ValidatedEnv;
29
+ /**
30
+ * Initialize environment validation - must be called at application startup
31
+ */
32
+ export declare function initializeEnvironment(): ValidatedEnv;
33
+ //# sourceMappingURL=env.d.ts.map