newo 1.9.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +68 -20
  3. package/dist/cli/commands/conversations.d.ts +3 -0
  4. package/dist/cli/commands/conversations.js +38 -0
  5. package/dist/cli/commands/help.d.ts +5 -0
  6. package/dist/cli/commands/help.js +50 -0
  7. package/dist/cli/commands/import-akb.d.ts +3 -0
  8. package/dist/cli/commands/import-akb.js +62 -0
  9. package/dist/cli/commands/list-customers.d.ts +3 -0
  10. package/dist/cli/commands/list-customers.js +13 -0
  11. package/dist/cli/commands/meta.d.ts +3 -0
  12. package/dist/cli/commands/meta.js +19 -0
  13. package/dist/cli/commands/pull-attributes.d.ts +3 -0
  14. package/dist/cli/commands/pull-attributes.js +16 -0
  15. package/dist/cli/commands/pull.d.ts +3 -0
  16. package/dist/cli/commands/pull.js +34 -0
  17. package/dist/cli/commands/push.d.ts +3 -0
  18. package/dist/cli/commands/push.js +39 -0
  19. package/dist/cli/commands/status.d.ts +3 -0
  20. package/dist/cli/commands/status.js +22 -0
  21. package/dist/cli/customer-selection.d.ts +23 -0
  22. package/dist/cli/customer-selection.js +110 -0
  23. package/dist/cli/errors.d.ts +9 -0
  24. package/dist/cli/errors.js +111 -0
  25. package/dist/cli.js +66 -463
  26. package/dist/fsutil.js +1 -1
  27. package/dist/sync/attributes.d.ts +7 -0
  28. package/dist/sync/attributes.js +90 -0
  29. package/dist/sync/conversations.d.ts +7 -0
  30. package/dist/sync/conversations.js +218 -0
  31. package/dist/sync/metadata.d.ts +8 -0
  32. package/dist/sync/metadata.js +124 -0
  33. package/dist/sync/projects.d.ts +13 -0
  34. package/dist/sync/projects.js +283 -0
  35. package/dist/sync/push.d.ts +7 -0
  36. package/dist/sync/push.js +171 -0
  37. package/dist/sync/skill-files.d.ts +42 -0
  38. package/dist/sync/skill-files.js +121 -0
  39. package/dist/sync/status.d.ts +6 -0
  40. package/dist/sync/status.js +247 -0
  41. package/dist/sync.d.ts +10 -8
  42. package/dist/sync.js +12 -1197
  43. package/dist/types.d.ts +0 -1
  44. package/package.json +2 -2
  45. package/src/cli/commands/conversations.ts +47 -0
  46. package/src/cli/commands/help.ts +50 -0
  47. package/src/cli/commands/import-akb.ts +71 -0
  48. package/src/cli/commands/list-customers.ts +14 -0
  49. package/src/cli/commands/meta.ts +26 -0
  50. package/src/cli/commands/pull-attributes.ts +23 -0
  51. package/src/cli/commands/pull.ts +43 -0
  52. package/src/cli/commands/push.ts +47 -0
  53. package/src/cli/commands/status.ts +30 -0
  54. package/src/cli/customer-selection.ts +135 -0
  55. package/src/cli/errors.ts +111 -0
  56. package/src/cli.ts +77 -471
  57. package/src/fsutil.ts +1 -1
  58. package/src/sync/attributes.ts +110 -0
  59. package/src/sync/conversations.ts +257 -0
  60. package/src/sync/metadata.ts +153 -0
  61. package/src/sync/projects.ts +359 -0
  62. package/src/sync/push.ts +200 -0
  63. package/src/sync/skill-files.ts +176 -0
  64. package/src/sync/status.ts +277 -0
  65. package/src/sync.ts +14 -1389
  66. package/src/types.ts +0 -1
package/src/cli.ts CHANGED
@@ -1,117 +1,22 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * NEWO CLI - Main entry point using modular architecture
4
+ */
2
5
  import minimist from 'minimist';
3
6
  import dotenv from 'dotenv';
4
- import { makeClient, getProjectMeta, importAkbArticle } from './api.js';
5
- import { pullAll, pushChanged, status, saveCustomerAttributes, pullConversations } from './sync.js';
6
- import { parseAkbFile, prepareArticlesForImport } from './akb.js';
7
- import { initializeEnvironment, ENV, EnvValidationError } from './env.js';
8
- import { parseCustomerConfigAsync, listCustomers, getCustomer, getDefaultCustomer, tryGetDefaultCustomer, getAllCustomers, validateCustomerConfig } from './customerAsync.js';
9
- import { getValidAccessToken } from './auth.js';
10
- import path from 'path';
11
- import type { CliArgs, NewoApiError, CustomerConfig } from './types.js';
12
-
13
- // Enhanced error logging for CLI
14
- function logCliError(level: 'error' | 'warn' | 'info', message: string, meta?: Record<string, unknown>): void {
15
- const timestamp = new Date().toISOString();
16
- const logEntry = {
17
- timestamp,
18
- level,
19
- module: 'cli',
20
- message,
21
- ...meta
22
- };
23
-
24
- // Only log JSON format in verbose mode, otherwise use clean user messages
25
- const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
26
-
27
- if (verbose) {
28
- if (level === 'error') {
29
- console.error(JSON.stringify(logEntry));
30
- } else if (level === 'warn') {
31
- console.warn(JSON.stringify(logEntry));
32
- } else {
33
- console.log(JSON.stringify(logEntry));
34
- }
35
- } else {
36
- // Clean user-facing messages
37
- if (level === 'error') {
38
- console.error(`āŒ ${message}`);
39
- } else if (level === 'warn') {
40
- console.warn(`āš ļø ${message}`);
41
- } else {
42
- console.log(`ā„¹ļø ${message}`);
43
- }
44
- }
45
- }
46
-
47
- // Enhanced error handling with user-friendly messages
48
- function handleCliError(error: unknown, operation: string = 'operation'): never {
49
- const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
50
-
51
- if (error instanceof Error) {
52
- // Authentication errors
53
- if (error.message.includes('API key') || error.message.includes('Authentication failed')) {
54
- logCliError('error', 'Authentication failed. Please check your API key configuration.');
55
- if (!verbose) {
56
- console.error('\nšŸ’” Troubleshooting tips:');
57
- console.error(' • Verify your API key is correct in .env file');
58
- console.error(' • For multi-customer setup, check NEWO_CUSTOMER_<IDN>_API_KEY');
59
- console.error(' • Run with --verbose for detailed error information');
60
- }
61
- }
62
- // Network errors
63
- else if (error.message.includes('Network timeout') || error.message.includes('ENOTFOUND') || error.message.includes('ECONNREFUSED')) {
64
- logCliError('error', 'Network connection failed. Please check your internet connection.');
65
- if (!verbose) {
66
- console.error('\nšŸ’” Troubleshooting tips:');
67
- console.error(' • Check your internet connection');
68
- console.error(' • Verify NEWO_BASE_URL is correct');
69
- console.error(' • Try again in a few moments');
70
- }
71
- }
72
- // Environment configuration errors
73
- else if (error instanceof EnvValidationError || error.message.includes('not set')) {
74
- logCliError('error', 'Configuration error. Please check your environment setup.');
75
- if (!verbose) {
76
- console.error('\nšŸ’” Setup help:');
77
- console.error(' • Copy .env.example to .env and configure your settings');
78
- console.error(' • Run "newo --help" to see configuration examples');
79
- console.error(' • Check the README for detailed setup instructions');
80
- }
81
- }
82
- // File system errors
83
- else if (error.message.includes('ENOENT') || error.message.includes('EACCES')) {
84
- logCliError('error', 'File system error. Please check file permissions and paths.');
85
- }
86
- // Rate limiting
87
- else if (error.message.includes('Rate limit exceeded')) {
88
- logCliError('error', 'Rate limit exceeded. Please wait before trying again.');
89
- }
90
- // General API errors
91
- else if (error.message.includes('response') || error.message.includes('status')) {
92
- logCliError('error', `API error during ${operation}. Please try again or contact support.`);
93
- }
94
- // Unknown errors
95
- else {
96
- logCliError('error', `Unexpected error during ${operation}: ${error.message}`);
97
- if (!verbose) {
98
- console.error('\nšŸ’” For more details, run the command with --verbose flag');
99
- }
100
- }
101
-
102
- if (verbose) {
103
- logCliError('error', 'Full error details', {
104
- operation,
105
- errorType: error.constructor.name,
106
- stack: error.stack?.split('\n').slice(0, 5).join('\n') // First 5 lines of stack
107
- });
108
- }
109
- } else {
110
- logCliError('error', `Unknown error during ${operation}: ${String(error)}`);
111
- }
112
-
113
- process.exit(1);
114
- }
7
+ import { initializeEnvironment, ENV } from './env.js';
8
+ import { parseAndValidateCustomerConfig } from './cli/customer-selection.js';
9
+ import { handleCliError, logCliError } from './cli/errors.js';
10
+ import { handlePullCommand } from './cli/commands/pull.js';
11
+ import { handlePushCommand } from './cli/commands/push.js';
12
+ import { handleStatusCommand } from './cli/commands/status.js';
13
+ import { handleConversationsCommand } from './cli/commands/conversations.js';
14
+ import { handleMetaCommand } from './cli/commands/meta.js';
15
+ import { handlePullAttributesCommand } from './cli/commands/pull-attributes.js';
16
+ import { handleImportAkbCommand } from './cli/commands/import-akb.js';
17
+ import { handleHelpCommand } from './cli/commands/help.js';
18
+ import { handleListCustomersCommand } from './cli/commands/list-customers.js';
19
+ import type { CliArgs, NewoApiError } from './types.js';
115
20
 
116
21
  dotenv.config();
117
22
 
@@ -120,400 +25,101 @@ async function main(): Promise<void> {
120
25
  // Initialize and validate environment at startup
121
26
  initializeEnvironment();
122
27
  } catch (error: unknown) {
123
- if (error instanceof EnvValidationError) {
124
- console.error('Environment validation failed:', error.message);
125
- process.exit(1);
126
- }
127
- throw error;
28
+ console.error('Environment validation failed:', error instanceof Error ? error.message : String(error));
29
+ process.exit(1);
128
30
  }
129
31
 
130
32
  const args = minimist(process.argv.slice(2)) as CliArgs;
131
33
  const cmd = args._[0];
132
34
  const verbose = Boolean(args.verbose || args.v);
133
-
134
- // Parse customer configuration (async for API key array support)
135
- let customerConfig;
136
- try {
137
- customerConfig = await parseCustomerConfigAsync(ENV as any, verbose);
138
- validateCustomerConfig(customerConfig);
139
- } catch (error: unknown) {
140
- logCliError('error', 'Failed to parse customer configuration');
141
- if (error instanceof Error) {
142
- logCliError('error', error.message);
143
- }
144
- process.exit(1);
145
- }
146
-
147
- // Handle customer selection
148
- let selectedCustomer: CustomerConfig | null = null;
149
- let allCustomers: CustomerConfig[] = [];
150
-
151
- if (cmd === 'list-customers') {
152
- const customers = listCustomers(customerConfig);
153
- console.log('Available customers:');
154
- for (const customerIdn of customers) {
155
- const isDefault = customerConfig.defaultCustomer === customerIdn;
156
- console.log(` ${customerIdn}${isDefault ? ' (default)' : ''}`);
157
- }
158
- return;
159
- }
160
-
161
- // Customer selection logic moved inside command processing to avoid early failures
162
35
 
163
36
  if (verbose) console.log(`šŸ” Command parsed: "${cmd}"`);
164
37
 
38
+ // Handle help command first
165
39
  if (!cmd || ['help', '-h', '--help'].includes(cmd)) {
166
- console.log(`NEWO CLI - Multi-Customer Support
167
- Usage:
168
- newo pull [--customer <idn>] # download projects -> ./newo_customers/<idn>/projects/
169
- newo push [--customer <idn>] # upload modified *.guidance/*.jinja back to NEWO
170
- newo status [--customer <idn>] # show modified files
171
- newo conversations [--customer <idn>] [--all] # download user conversations -> ./newo_customers/<idn>/conversations.yaml
172
- newo list-customers # list available customers
173
- newo meta [--customer <idn>] # get project metadata (debug)
174
- newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles from file
175
-
176
- Flags:
177
- --customer <idn> # specify customer (if not set, uses default or interactive selection)
178
- --all # include all available data (for conversations: all personas and acts)
179
- --verbose, -v # enable detailed logging
180
-
181
- Environment Variables:
182
- NEWO_BASE_URL # NEWO API base URL (default: https://app.newo.ai)
183
- NEWO_CUSTOMER_<IDN>_API_KEY # API key for customer <IDN>
184
- NEWO_CUSTOMER_<IDN>_PROJECT_ID # Optional: specific project ID for customer
185
- NEWO_DEFAULT_CUSTOMER # Optional: default customer to use
186
-
187
- Multi-Customer Examples:
188
- # Configure customers in .env:
189
- NEWO_CUSTOMER_acme_API_KEY=your_acme_api_key
190
- NEWO_CUSTOMER_globex_API_KEY=your_globex_api_key
191
- NEWO_DEFAULT_CUSTOMER=acme
192
-
193
- # Commands:
194
- newo pull # Pull from all customers (if no default set)
195
- newo pull --customer acme # Pull projects for Acme only
196
- newo status # Status for all customers (if no default set)
197
- newo push # Interactive selection for multiple customers
198
- newo push --customer globex # Push changes for Globex only
199
-
200
- File Structure:
201
- newo_customers/
202
- ā”œā”€ā”€ acme/
203
- │ └── projects/
204
- │ └── project1/
205
- └── globex/
206
- └── projects/
207
- └── project2/
208
- `);
209
- return;
210
- }
211
-
212
- if (verbose) console.log(`šŸ” Starting command processing for: ${cmd}`);
213
-
214
- if (cmd === 'pull') {
215
- // Handle customer selection for pull command
216
- if (args.customer) {
217
- const customer = getCustomer(customerConfig, args.customer as string);
218
- if (!customer) {
219
- console.error(`Unknown customer: ${args.customer}`);
220
- console.error(`Available customers: ${listCustomers(customerConfig).join(', ')}`);
221
- process.exit(1);
222
- }
223
- selectedCustomer = customer;
224
- } else {
225
- // Try to get default, fall back to all customers
226
- selectedCustomer = tryGetDefaultCustomer(customerConfig);
227
- if (!selectedCustomer) {
228
- allCustomers = getAllCustomers(customerConfig);
229
- if (verbose) console.log(`šŸ“„ No default customer specified, pulling from all ${allCustomers.length} customers`);
230
- }
231
- }
232
-
233
- if (selectedCustomer) {
234
- // Single customer pull
235
- const accessToken = await getValidAccessToken(selectedCustomer);
236
- const client = await makeClient(verbose, accessToken);
237
- const projectId = selectedCustomer.projectId || null;
238
- await pullAll(client, selectedCustomer, projectId, verbose);
239
- } else if (allCustomers.length > 0) {
240
- // Multi-customer pull
241
- console.log(`šŸ”„ Pulling from ${allCustomers.length} customers...`);
242
- for (const customer of allCustomers) {
243
- console.log(`\nšŸ“„ Pulling from customer: ${customer.idn}`);
244
- const accessToken = await getValidAccessToken(customer);
245
- const client = await makeClient(verbose, accessToken);
246
- const projectId = customer.projectId || null;
247
- await pullAll(client, customer, projectId, verbose);
248
- }
249
- console.log(`\nāœ… Pull completed for all ${allCustomers.length} customers`);
250
- }
251
- return;
252
- }
253
-
254
- if (cmd === 'status') {
255
- // Handle customer selection for status command
256
- if (args.customer) {
257
- const customer = getCustomer(customerConfig, args.customer as string);
258
- if (!customer) {
259
- console.error(`Unknown customer: ${args.customer}`);
260
- console.error(`Available customers: ${listCustomers(customerConfig).join(', ')}`);
261
- process.exit(1);
262
- }
263
- selectedCustomer = customer;
264
- } else {
265
- // Try to get default, fall back to all customers
266
- selectedCustomer = tryGetDefaultCustomer(customerConfig);
267
- if (!selectedCustomer) {
268
- allCustomers = getAllCustomers(customerConfig);
269
- console.log(`šŸ”„ Checking status for ${allCustomers.length} customers...`);
270
- }
271
- }
272
-
273
- if (selectedCustomer) {
274
- // Single customer status
275
- await status(selectedCustomer, verbose);
276
- } else if (allCustomers.length > 0) {
277
- // Multi-customer status
278
- for (const customer of allCustomers) {
279
- console.log(`\nšŸ“‹ Status for customer: ${customer.idn}`);
280
- await status(customer, verbose);
281
- }
282
- console.log(`\nāœ… Status check completed for all ${allCustomers.length} customers`);
283
- }
284
- return;
285
- }
286
-
287
- if (cmd === 'push') {
288
- // Handle customer selection for push command
289
- if (args.customer) {
290
- const customer = getCustomer(customerConfig, args.customer as string);
291
- if (!customer) {
292
- console.error(`Unknown customer: ${args.customer}`);
293
- console.error(`Available customers: ${listCustomers(customerConfig).join(', ')}`);
294
- process.exit(1);
295
- }
296
- selectedCustomer = customer;
297
- } else {
298
- // Try to get default, provide interactive selection if multiple exist
299
- selectedCustomer = tryGetDefaultCustomer(customerConfig);
300
- if (!selectedCustomer) {
301
- // Multiple customers exist with no default, ask user
302
- allCustomers = getAllCustomers(customerConfig);
303
- console.log(`\nšŸ“¤ Multiple customers available for push:`);
304
- allCustomers.forEach((customer, index) => {
305
- console.log(` ${index + 1}. ${customer.idn}`);
306
- });
307
- console.log(` ${allCustomers.length + 1}. All customers`);
308
-
309
- const readline = await import('readline');
310
- const rl = readline.createInterface({
311
- input: process.stdin,
312
- output: process.stdout
313
- });
314
-
315
- const choice = await new Promise<string>((resolve) => {
316
- rl.question(`\nSelect customer to push (1-${allCustomers.length + 1}): `, resolve);
317
- });
318
- rl.close();
319
-
320
- const choiceNum = parseInt(choice.trim());
321
- if (choiceNum === allCustomers.length + 1) {
322
- // User selected "All customers"
323
- console.log(`šŸ”„ Pushing to all ${allCustomers.length} customers...`);
324
- } else if (choiceNum >= 1 && choiceNum <= allCustomers.length) {
325
- // User selected specific customer
326
- selectedCustomer = allCustomers[choiceNum - 1] || null;
327
- allCustomers = []; // Clear to indicate single customer mode
328
- if (selectedCustomer) {
329
- console.log(`šŸ”„ Pushing to customer: ${selectedCustomer.idn}`);
330
- }
331
- } else {
332
- console.error('Invalid choice. Exiting.');
333
- process.exit(1);
334
- }
335
- }
336
- }
337
-
338
- if (selectedCustomer) {
339
- // Single customer push
340
- const accessToken = await getValidAccessToken(selectedCustomer);
341
- const client = await makeClient(verbose, accessToken);
342
- await pushChanged(client, selectedCustomer, verbose);
343
- } else if (allCustomers.length > 0) {
344
- // Multi-customer push (user selected "All customers")
345
- console.log(`šŸ”„ Pushing to ${allCustomers.length} customers...`);
346
- for (const customer of allCustomers) {
347
- console.log(`\nšŸ“¤ Pushing for customer: ${customer.idn}`);
348
- const accessToken = await getValidAccessToken(customer);
349
- const client = await makeClient(verbose, accessToken);
350
- await pushChanged(client, customer, verbose);
351
- }
352
- console.log(`\nāœ… Push completed for all ${allCustomers.length} customers`);
353
- }
354
- return;
355
- }
356
-
357
- if (cmd === 'conversations') {
358
- // Handle customer selection for conversations command
359
- if (args.customer) {
360
- const customer = getCustomer(customerConfig, args.customer as string);
361
- if (!customer) {
362
- console.error(`Unknown customer: ${args.customer}`);
363
- console.error(`Available customers: ${listCustomers(customerConfig).join(', ')}`);
364
- process.exit(1);
365
- }
366
- selectedCustomer = customer;
367
- } else {
368
- // Try to get default, fall back to all customers
369
- selectedCustomer = tryGetDefaultCustomer(customerConfig);
370
- if (!selectedCustomer) {
371
- allCustomers = getAllCustomers(customerConfig);
372
- if (verbose) console.log(`šŸ’¬ No default customer specified, pulling conversations from all ${allCustomers.length} customers`);
373
- }
374
- }
375
-
376
- // Parse conversation-specific options - load all data by default
377
- const conversationOptions = {
378
- includeAll: true, // Always include all data for conversations
379
- maxPersonas: undefined, // No limit on personas
380
- maxActsPerPersona: undefined // No limit on acts per persona
381
- };
382
-
383
- if (selectedCustomer) {
384
- // Single customer conversations
385
- const accessToken = await getValidAccessToken(selectedCustomer);
386
- const client = await makeClient(verbose, accessToken);
387
- console.log(`šŸ’¬ Pulling conversations for customer: ${selectedCustomer.idn} (all data)`);
388
- await pullConversations(client, selectedCustomer, conversationOptions, verbose);
389
- console.log(`āœ… Conversations saved to newo_customers/${selectedCustomer.idn}/conversations.yaml`);
390
- } else if (allCustomers.length > 0) {
391
- // Multi-customer conversations
392
- console.log(`šŸ’¬ Pulling conversations from ${allCustomers.length} customers (all data)...`);
393
- for (const customer of allCustomers) {
394
- console.log(`\nšŸ’¬ Pulling conversations for customer: ${customer.idn}`);
395
- const accessToken = await getValidAccessToken(customer);
396
- const client = await makeClient(verbose, accessToken);
397
- await pullConversations(client, customer, conversationOptions, verbose);
398
- }
399
- console.log(`\nāœ… Conversations pull completed for all ${allCustomers.length} customers`);
400
- }
40
+ handleHelpCommand();
401
41
  return;
402
42
  }
403
43
 
404
- // For all other commands, require a single selected customer
405
- if (args.customer) {
406
- const customer = getCustomer(customerConfig, args.customer as string);
407
- if (!customer) {
408
- console.error(`Unknown customer: ${args.customer}`);
409
- console.error(`Available customers: ${listCustomers(customerConfig).join(', ')}`);
410
- process.exit(1);
411
- }
412
- selectedCustomer = customer;
413
- } else {
44
+ // Handle list-customers command (doesn't need full customer config)
45
+ if (cmd === 'list-customers') {
414
46
  try {
415
- selectedCustomer = getDefaultCustomer(customerConfig);
47
+ const customerConfig = await parseAndValidateCustomerConfig(ENV as any, verbose);
48
+ handleListCustomersCommand(customerConfig);
49
+ return;
416
50
  } catch (error: unknown) {
417
- const message = error instanceof Error ? error.message : String(error);
418
- console.error(message);
419
- process.exit(1);
51
+ handleCliError(error, 'list-customers');
420
52
  }
421
53
  }
422
54
 
423
- if (!selectedCustomer) {
424
- console.error('Customer selection required for this command');
425
- process.exit(1);
426
- }
55
+ // For all other commands, parse and validate customer configuration
56
+ const customerConfig = await parseAndValidateCustomerConfig(ENV as any, verbose);
427
57
 
428
- // Get access token for the selected customer
429
- const accessToken = await getValidAccessToken(selectedCustomer);
430
- const client = await makeClient(verbose, accessToken);
58
+ if (verbose) console.log(`šŸ” Starting command processing for: ${cmd}`);
431
59
 
432
- if (cmd === 'meta') {
433
- if (!selectedCustomer.projectId) {
434
- console.error(`No project ID configured for customer ${selectedCustomer.idn}`);
435
- console.error(`Set NEWO_CUSTOMER_${selectedCustomer.idn.toUpperCase()}_PROJECT_ID in your .env file`);
436
- process.exit(1);
437
- }
438
- const meta = await getProjectMeta(client, selectedCustomer.projectId);
439
- console.log(JSON.stringify(meta, null, 2));
440
- } else if (cmd === 'pull-attributes') {
441
- console.log(`šŸ” Fetching customer attributes for ${selectedCustomer.idn}...`);
442
- await saveCustomerAttributes(client, selectedCustomer, verbose);
443
- console.log(`āœ… Customer attributes saved to newo_customers/${selectedCustomer.idn}/attributes.yaml`);
444
- } else if (cmd === 'import-akb') {
445
- const akbFile = args._[1];
446
- const personaId = args._[2];
447
-
448
- if (!akbFile || !personaId) {
449
- console.error('Usage: newo import-akb <file> <persona_id>');
450
- console.error('Example: newo import-akb akb.txt da4550db-2b95-4500-91ff-fb4b60fe7be9');
451
- process.exit(1);
452
- }
453
-
454
- const filePath = path.resolve(akbFile);
455
-
456
- try {
457
- if (verbose) console.log(`šŸ“– Parsing AKB file: ${filePath}`);
458
- const articles = await parseAkbFile(filePath);
459
- console.log(`āœ“ Parsed ${articles.length} articles from ${akbFile}`);
460
-
461
- if (verbose) console.log(`šŸ”§ Preparing articles for persona: ${personaId}`);
462
- const preparedArticles = prepareArticlesForImport(articles, personaId);
463
-
464
- let successCount = 0;
465
- let errorCount = 0;
466
-
467
- console.log(`šŸ“¤ Importing ${preparedArticles.length} articles...`);
468
-
469
- for (const [index, article] of preparedArticles.entries()) {
470
- try {
471
- if (verbose) {
472
- console.log(` [${index + 1}/${preparedArticles.length}] Importing ${article.topic_name}...`);
473
- }
474
- await importAkbArticle(client, article);
475
- successCount++;
476
- if (!verbose) process.stdout.write('.');
477
- } catch (error: unknown) {
478
- errorCount++;
479
- const errorMessage = error instanceof Error && 'response' in error
480
- ? (error as NewoApiError)?.response?.data
481
- : error instanceof Error
482
- ? error.message
483
- : String(error);
484
- console.error(`\nāŒ Failed to import ${article.topic_name}:`, errorMessage);
485
- }
486
- }
487
-
488
- if (!verbose) console.log(''); // new line after dots
489
- console.log(`āœ… Import complete: ${successCount} successful, ${errorCount} failed`);
490
-
491
- } catch (error: unknown) {
492
- const message = error instanceof Error ? error.message : String(error);
493
- console.error('āŒ AKB import failed:', message);
494
- process.exit(1);
60
+ try {
61
+ switch (cmd) {
62
+ case 'pull':
63
+ await handlePullCommand(customerConfig, args, verbose);
64
+ break;
65
+
66
+ case 'push':
67
+ await handlePushCommand(customerConfig, args, verbose);
68
+ break;
69
+
70
+ case 'status':
71
+ await handleStatusCommand(customerConfig, args, verbose);
72
+ break;
73
+
74
+ case 'conversations':
75
+ await handleConversationsCommand(customerConfig, args, verbose);
76
+ break;
77
+
78
+ case 'meta':
79
+ await handleMetaCommand(customerConfig, args, verbose);
80
+ break;
81
+
82
+ case 'pull-attributes':
83
+ await handlePullAttributesCommand(customerConfig, args, verbose);
84
+ break;
85
+
86
+ case 'import-akb':
87
+ await handleImportAkbCommand(customerConfig, args, verbose);
88
+ break;
89
+
90
+ default:
91
+ console.error('Unknown command:', cmd);
92
+ console.error('Run "newo --help" for usage information');
93
+ process.exit(1);
495
94
  }
496
- } else {
497
- console.error('Unknown command:', cmd);
498
- process.exit(1);
95
+ } catch (error: unknown) {
96
+ handleCliError(error, cmd);
499
97
  }
500
98
  }
501
99
 
502
- main().catch((error: unknown) => {
100
+ // Global error handler
101
+ process.on('unhandledRejection', (error: unknown) => {
503
102
  // Determine operation context from command line args
504
103
  const args = process.argv.slice(2);
505
104
  const cmd = args.find(arg => !arg.startsWith('-')) || 'unknown command';
506
-
105
+
507
106
  // Handle API errors with specific data
508
107
  if (error instanceof Error && 'response' in error) {
509
108
  const apiError = error as NewoApiError;
510
109
  const responseData = apiError.response?.data;
511
110
  const status = apiError.response?.status;
512
-
111
+
513
112
  if (responseData && status) {
514
113
  logCliError('error', `API error (${status}): ${JSON.stringify(responseData)}`);
515
114
  }
516
115
  }
517
-
116
+
117
+ handleCliError(error, cmd);
118
+ });
119
+
120
+ // Start the CLI
121
+ main().catch((error: unknown) => {
122
+ const args = process.argv.slice(2);
123
+ const cmd = args.find(arg => !arg.startsWith('-')) || 'unknown command';
518
124
  handleCliError(error, cmd);
519
125
  });
package/src/fsutil.ts CHANGED
@@ -84,7 +84,7 @@ export function skillScriptPath(
84
84
  runnerType: RunnerType = 'guidance'
85
85
  ): string {
86
86
  const extension = runnerType === 'nsl' ? '.jinja' : '.guidance';
87
- return path.posix.join(skillFolderPath(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn), `skill${extension}`);
87
+ return path.posix.join(skillFolderPath(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn), `${skillIdn}${extension}`);
88
88
  }
89
89
 
90
90
  // Metadata paths for hierarchical structure