newo 1.5.2 → 1.6.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.
package/README.md CHANGED
@@ -127,7 +127,7 @@ NEWO_REFRESH_URL=custom_refresh_endpoint # Custom refresh endpoint
127
127
 
128
128
  | Command | Description | Examples |
129
129
  |---------|-------------|----------|
130
- | `newo pull` | Download projects from NEWO | `newo pull`<br>`newo pull --customer=ACME`<br>`newo pull --project=uuid` |
130
+ | `newo pull` | Download projects from NEWO | `newo pull` (all customers if no default)<br>`newo pull --customer=ACME`<br>`newo pull --project=uuid` |
131
131
  | `newo push` | Upload local changes to NEWO | `newo push`<br>`newo push --customer=BETA` |
132
132
  | `newo status` | Show modified files | `newo status`<br>`newo status --verbose` |
133
133
  | `newo list-customers` | List configured customers | `newo list-customers` |
@@ -146,8 +146,8 @@ newo pull --customer=NEWO_ABC123
146
146
  # Push changes to specific customer
147
147
  newo push --customer=NEWO_XYZ789
148
148
 
149
- # Work with default customer (all projects)
150
- newo pull # Uses default or prompts for selection
149
+ # Work with default customer (or auto multi-customer)
150
+ newo pull # Uses default customer OR pulls from all customers if no default set
151
151
  newo push # Pushes to appropriate customers based on file origin
152
152
  ```
153
153
 
@@ -493,6 +493,176 @@ npm run dev push # Build and run push
493
493
  | `npm run test:unit` | Run unit tests only |
494
494
  | `npm run test:coverage` | Generate coverage report |
495
495
 
496
+ ### Makefile Commands
497
+
498
+ The project includes a comprehensive Makefile for streamlined development:
499
+
500
+ #### Quick Commands
501
+ ```bash
502
+ make help # Show all available commands
503
+ make setup # Initial project setup
504
+ make build # Build TypeScript
505
+ make test # Run tests
506
+ make dev # Development mode
507
+ make publish # Publish to GitHub and NPM
508
+ ```
509
+
510
+ #### Development Workflow
511
+ ```bash
512
+ make fresh-start # Clean + install + build + test
513
+ make dev-pull # Test pull command in development
514
+ make dev-push # Test push command in development
515
+ make test-local # Comprehensive local testing
516
+ ```
517
+
518
+ #### Publishing Workflow
519
+ ```bash
520
+ make pre-publish # Complete validation before publishing
521
+ make publish-github # Publish to GitHub with release
522
+ make publish-npm # Publish to NPM
523
+ make publish # Publish to both platforms
524
+ ```
525
+
526
+ #### Quality Assurance
527
+ ```bash
528
+ make typecheck # TypeScript type checking
529
+ make lint # Code linting
530
+ make check-all # All quality checks
531
+ make deps-audit # Security audit
532
+ ```
533
+
534
+ ### Local Testing
535
+
536
+ After making changes to the CLI code, proper testing is essential to ensure functionality works correctly.
537
+
538
+ #### Quick Testing Commands
539
+
540
+ ```bash
541
+ # Build and test core functionality
542
+ npm run build # Compile TypeScript
543
+ node ./dist/cli.js --help # Test CLI loads correctly
544
+ node ./dist/cli.js list-customers # Test customer configuration
545
+
546
+ # Test single customer operations
547
+ node ./dist/cli.js pull --customer=CUSTOMER_IDN # Test specific customer pull
548
+ node ./dist/cli.js status --customer=CUSTOMER_IDN # Test specific customer status
549
+
550
+ # Test multi-customer operations (if multiple API keys configured)
551
+ node ./dist/cli.js pull # Test auto multi-customer pull
552
+ node ./dist/cli.js pull --verbose # Test with detailed logging
553
+ ```
554
+
555
+ #### Complete Testing Workflow
556
+
557
+ 1. **Environment Setup**
558
+ ```bash
559
+ # Ensure clean environment
560
+ cp .env.example .env
561
+ # Edit .env with your API key(s)
562
+ ```
563
+
564
+ 2. **Build & Syntax Check**
565
+ ```bash
566
+ npm run build # Must complete without TypeScript errors
567
+ npm run typecheck # Verify type safety
568
+ ```
569
+
570
+ 3. **Basic CLI Tests**
571
+ ```bash
572
+ node ./dist/cli.js --help # Should show updated help text
573
+ node ./dist/cli.js list-customers # Should show configured customers
574
+ ```
575
+
576
+ 4. **Authentication Tests**
577
+ ```bash
578
+ # Test API key exchange and token generation
579
+ node ./dist/cli.js meta --verbose # Forces authentication
580
+ ```
581
+
582
+ 5. **Pull Operation Tests**
583
+ ```bash
584
+ # Single customer (if specific customer configured)
585
+ node ./dist/cli.js pull --customer=YOUR_CUSTOMER_IDN --verbose
586
+
587
+ # Multi-customer (if multiple API keys configured)
588
+ node ./dist/cli.js pull --verbose # Should pull from all customers
589
+
590
+ # Check file structure was created correctly
591
+ ls -la newo_customers/ # Should show customer folders
592
+ ```
593
+
594
+ 6. **Status & Push Tests**
595
+ ```bash
596
+ node ./dist/cli.js status --verbose # Should show no changes initially
597
+
598
+ # Make a test change to a .guidance or .jinja file
599
+ echo "# Test comment" >> newo_customers/*/projects/*/*/*/*.guidance
600
+
601
+ node ./dist/cli.js status # Should detect the change
602
+ node ./dist/cli.js push --verbose # Should upload the change
603
+ ```
604
+
605
+ #### Testing Multi-Customer Functionality
606
+
607
+ If you have multiple API keys configured, test the new auto-pull behavior:
608
+
609
+ ```bash
610
+ # Test that pull works without specifying customer
611
+ node ./dist/cli.js pull # Should pull from ALL customers
612
+
613
+ # Test individual customer selection still works
614
+ node ./dist/cli.js pull --customer=CUSTOMER_A # Should pull from specific customer
615
+ node ./dist/cli.js push --customer=CUSTOMER_B # Should push to specific customer
616
+ ```
617
+
618
+ #### Common Testing Issues & Solutions
619
+
620
+ **Issue: "Multiple customers configured but no default specified" error**
621
+ - **Cause**: You're using `npx newo` instead of the local build
622
+ - **Solution**: Use `node ./dist/cli.js` instead of `npx newo`
623
+
624
+ **Issue: Changes not reflected in CLI behavior**
625
+ - **Cause**: TypeScript not compiled or using cached version
626
+ - **Solution**: Run `npm run build` first, then test with `node ./dist/cli.js`
627
+
628
+ **Issue: Authentication errors during testing**
629
+ - **Cause**: Invalid API keys or network issues
630
+ - **Solution**: Verify API keys in `.env`, test with `--verbose` flag for details
631
+
632
+ **Issue: File permission errors**
633
+ - **Cause**: Insufficient permissions in project directory
634
+ - **Solution**: Ensure write permissions: `chmod 755 .` and check disk space
635
+
636
+ #### Performance Testing
637
+
638
+ For testing with large projects or multiple customers:
639
+
640
+ ```bash
641
+ # Test with timeout to avoid hanging
642
+ timeout 30s node ./dist/cli.js pull --verbose # Should complete or show progress
643
+
644
+ # Test memory usage
645
+ node --max-old-space-size=512 ./dist/cli.js pull # Test with limited memory
646
+ ```
647
+
648
+ #### Integration Testing
649
+
650
+ Test complete workflows that users would actually perform:
651
+
652
+ ```bash
653
+ # Complete development workflow
654
+ node ./dist/cli.js pull # Download latest
655
+ # Edit some .guidance/.jinja files
656
+ node ./dist/cli.js status # Check changes
657
+ node ./dist/cli.js push # Upload changes
658
+
659
+ # Multi-customer workflow
660
+ node ./dist/cli.js list-customers # See available customers
661
+ node ./dist/cli.js pull --customer=CUSTOMER_A # Work with specific customer
662
+ # Make changes
663
+ node ./dist/cli.js push --customer=CUSTOMER_A # Push to specific customer
664
+ ```
665
+
496
666
  ### Project Architecture
497
667
 
498
668
  ```
@@ -535,6 +705,157 @@ NEWO CLI includes comprehensive test coverage:
535
705
 
536
706
  ---
537
707
 
708
+ ## Publishing & Release Management
709
+
710
+ The project includes automated scripts for publishing to GitHub and NPM with proper validation and release management.
711
+
712
+ ### Prerequisites for Publishing
713
+
714
+ 1. **GitHub Setup**
715
+ ```bash
716
+ # Ensure GitHub remote is configured
717
+ git remote -v # Should show origin pointing to sabbah13/newo-cli
718
+
719
+ # Install GitHub CLI (optional, for automatic releases)
720
+ brew install gh # macOS
721
+ # or
722
+ sudo apt install gh # Ubuntu
723
+ ```
724
+
725
+ 2. **NPM Setup**
726
+ ```bash
727
+ # Login to NPM
728
+ npm login
729
+ npm whoami # Verify you're logged in
730
+ ```
731
+
732
+ ### Publishing Workflow
733
+
734
+ #### Option 1: Full Automated Publishing (Recommended)
735
+ ```bash
736
+ # Complete validation and publish to both platforms
737
+ make publish
738
+ ```
739
+
740
+ This command will:
741
+ - Run all tests and quality checks
742
+ - Build the project
743
+ - Prompt for version bump (patch/minor/major)
744
+ - Publish to GitHub with release notes
745
+ - Publish to NPM with proper tags
746
+ - Verify publication success
747
+
748
+ #### Option 2: Step-by-Step Publishing
749
+ ```bash
750
+ # 1. Validate everything is ready
751
+ make pre-publish
752
+
753
+ # 2. Publish to GitHub first
754
+ make publish-github
755
+
756
+ # 3. Publish to NPM
757
+ make publish-npm
758
+ ```
759
+
760
+ #### Option 3: Manual Publishing
761
+ ```bash
762
+ # Run individual scripts
763
+ ./scripts/publish-github.sh
764
+ ./scripts/publish-npm.sh
765
+ ```
766
+
767
+ ### Version Management
768
+
769
+ Use semantic versioning with the Makefile helpers:
770
+
771
+ ```bash
772
+ make version-patch # 1.5.2 → 1.5.3 (bug fixes)
773
+ make version-minor # 1.5.2 → 1.6.0 (new features)
774
+ make version-major # 1.5.2 → 2.0.0 (breaking changes)
775
+ ```
776
+
777
+ ### Pre-Release Publishing
778
+
779
+ For beta/alpha releases:
780
+ ```bash
781
+ # Set pre-release version manually
782
+ npm version 1.6.0-beta.1 --no-git-tag-version
783
+
784
+ # Publish with beta tag
785
+ make publish-npm # Automatically detects pre-release and uses beta tag
786
+ ```
787
+
788
+ ### Publishing Checklist
789
+
790
+ Before publishing, ensure:
791
+ - ✅ All tests pass (`make test`)
792
+ - ✅ TypeScript compiles without errors (`make build`)
793
+ - ✅ Local testing completed (`make test-local`)
794
+ - ✅ Documentation is up to date
795
+ - ✅ CHANGELOG.md is updated (if exists)
796
+ - ✅ Version number is appropriate
797
+ - ✅ No uncommitted changes (or committed)
798
+
799
+ ### Automated Validation
800
+
801
+ The publish scripts include comprehensive validation:
802
+ - **TypeScript compilation** and type checking
803
+ - **Test suite execution** with coverage requirements
804
+ - **Package size analysis** and content verification
805
+ - **Authentication verification** for GitHub and NPM
806
+ - **Version conflict detection** to prevent duplicate publishes
807
+ - **Security audit** of dependencies
808
+
809
+ ### GitHub Release Features
810
+
811
+ The GitHub publish script automatically:
812
+ - Creates semantic version tags (`v1.5.3`)
813
+ - Generates comprehensive release notes
814
+ - Marks releases as "latest" on GitHub
815
+ - Links to NPM package and documentation
816
+ - Includes installation instructions
817
+
818
+ ### NPM Package Features
819
+
820
+ The NPM publish script ensures:
821
+ - Proper package.json validation
822
+ - Binary CLI availability verification
823
+ - File inclusion/exclusion validation
824
+ - Pre-release tag detection (`beta`, `alpha`, `rc`)
825
+ - Post-publish verification
826
+
827
+ ### Rollback Procedures
828
+
829
+ If issues are discovered after publishing:
830
+
831
+ **NPM Rollback:**
832
+ ```bash
833
+ # Deprecate problematic version
834
+ npm deprecate newo@1.5.3 "Version has known issues, use 1.5.2 instead"
835
+
836
+ # Publish fixed version immediately
837
+ make version-patch
838
+ make publish-npm
839
+ ```
840
+
841
+ **GitHub Rollback:**
842
+ ```bash
843
+ # Delete tag and release (if needed)
844
+ git tag -d v1.5.3
845
+ git push origin :refs/tags/v1.5.3
846
+ gh release delete v1.5.3
847
+ ```
848
+
849
+ ### Monitoring Post-Publication
850
+
851
+ After publishing, monitor:
852
+ - **NPM downloads**: https://npmjs.com/package/newo
853
+ - **GitHub releases**: https://github.com/sabbah13/newo-cli/releases
854
+ - **Issue reports**: https://github.com/sabbah13/newo-cli/issues
855
+ - **Badge updates**: README badges should reflect new version
856
+
857
+ ---
858
+
538
859
  ## Contributing
539
860
 
540
861
  We welcome contributions to NEWO CLI! Here's how to get involved:
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ 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
7
  import { initializeEnvironment, ENV, EnvValidationError } from './env.js';
8
- import { parseCustomerConfigAsync, listCustomers, getCustomer, getDefaultCustomer, validateCustomerConfig } from './customerAsync.js';
8
+ import { parseCustomerConfigAsync, listCustomers, getCustomer, getDefaultCustomer, tryGetDefaultCustomer, getAllCustomers, validateCustomerConfig } from './customerAsync.js';
9
9
  import { getValidAccessToken } from './auth.js';
10
10
  import path from 'path';
11
11
  // Enhanced error logging for CLI
@@ -126,7 +126,7 @@ async function main() {
126
126
  const args = minimist(process.argv.slice(2));
127
127
  const cmd = args._[0];
128
128
  const verbose = Boolean(args.verbose || args.v);
129
- // Parse customer configuration (async for API key array support)
129
+ // Parse customer configuration (async for API key array support)
130
130
  let customerConfig;
131
131
  try {
132
132
  customerConfig = await parseCustomerConfigAsync(ENV, verbose);
@@ -140,7 +140,8 @@ async function main() {
140
140
  process.exit(1);
141
141
  }
142
142
  // Handle customer selection
143
- let selectedCustomer;
143
+ let selectedCustomer = null;
144
+ let allCustomers = [];
144
145
  if (cmd === 'list-customers') {
145
146
  const customers = listCustomers(customerConfig);
146
147
  console.log('Available customers:');
@@ -160,13 +161,33 @@ async function main() {
160
161
  selectedCustomer = customer;
161
162
  }
162
163
  else {
163
- try {
164
- selectedCustomer = getDefaultCustomer(customerConfig);
164
+ // For pull command, try to get default but fall back to all customers if multiple exist
165
+ if (cmd === 'pull') {
166
+ try {
167
+ selectedCustomer = tryGetDefaultCustomer(customerConfig);
168
+ if (!selectedCustomer) {
169
+ // Multiple customers exist with no default, pull from all
170
+ allCustomers = getAllCustomers(customerConfig);
171
+ if (verbose)
172
+ console.log(`📥 No default customer specified, pulling from all ${allCustomers.length} customers`);
173
+ }
174
+ }
175
+ catch (error) {
176
+ const message = error instanceof Error ? error.message : String(error);
177
+ console.error(message);
178
+ process.exit(1);
179
+ }
165
180
  }
166
- catch (error) {
167
- const message = error instanceof Error ? error.message : String(error);
168
- console.error(message);
169
- process.exit(1);
181
+ else {
182
+ // For other commands, require explicit customer selection
183
+ try {
184
+ selectedCustomer = getDefaultCustomer(customerConfig);
185
+ }
186
+ catch (error) {
187
+ const message = error instanceof Error ? error.message : String(error);
188
+ console.error(message);
189
+ process.exit(1);
190
+ }
170
191
  }
171
192
  }
172
193
  if (!cmd || ['help', '-h', '--help'].includes(cmd)) {
@@ -178,28 +199,29 @@ Usage:
178
199
  newo list-customers # list available customers
179
200
  newo meta [--customer <idn>] # get project metadata (debug)
180
201
  newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles from file
181
-
202
+
182
203
  Flags:
183
- --customer <idn> # specify customer (if not set, uses default)
204
+ --customer <idn> # specify customer (if not set, uses default or all for pull)
184
205
  --verbose, -v # enable detailed logging
185
-
206
+
186
207
  Environment Variables:
187
208
  NEWO_BASE_URL # NEWO API base URL (default: https://app.newo.ai)
188
209
  NEWO_CUSTOMER_<IDN>_API_KEY # API key for customer <IDN>
189
210
  NEWO_CUSTOMER_<IDN>_PROJECT_ID # Optional: specific project ID for customer
190
211
  NEWO_DEFAULT_CUSTOMER # Optional: default customer to use
191
-
212
+
192
213
  Multi-Customer Examples:
193
214
  # Configure customers in .env:
194
215
  NEWO_CUSTOMER_acme_API_KEY=your_acme_api_key
195
216
  NEWO_CUSTOMER_globex_API_KEY=your_globex_api_key
196
217
  NEWO_DEFAULT_CUSTOMER=acme
197
-
218
+
198
219
  # Commands:
199
- newo pull --customer acme # Pull projects for Acme
220
+ newo pull # Pull from all customers (if no default set)
221
+ newo pull --customer acme # Pull projects for Acme only
200
222
  newo push --customer globex # Push changes for Globex
201
223
  newo status # Status for default customer
202
-
224
+
203
225
  File Structure:
204
226
  newo_customers/
205
227
  ├── acme/
@@ -211,15 +233,37 @@ File Structure:
211
233
  `);
212
234
  return;
213
235
  }
236
+ if (cmd === 'pull') {
237
+ if (selectedCustomer) {
238
+ // Single customer pull
239
+ const accessToken = await getValidAccessToken(selectedCustomer);
240
+ const client = await makeClient(verbose, accessToken);
241
+ const projectId = selectedCustomer.projectId || null;
242
+ await pullAll(client, selectedCustomer, projectId, verbose);
243
+ }
244
+ else if (allCustomers.length > 0) {
245
+ // Multi-customer pull
246
+ console.log(`🔄 Pulling from ${allCustomers.length} customers...`);
247
+ for (const customer of allCustomers) {
248
+ console.log(`\n📥 Pulling from customer: ${customer.idn}`);
249
+ const accessToken = await getValidAccessToken(customer);
250
+ const client = await makeClient(verbose, accessToken);
251
+ const projectId = customer.projectId || null;
252
+ await pullAll(client, customer, projectId, verbose);
253
+ }
254
+ console.log(`\n✅ Pull completed for all ${allCustomers.length} customers`);
255
+ }
256
+ return;
257
+ }
258
+ // For all other commands, require a single selected customer
259
+ if (!selectedCustomer) {
260
+ console.error('Customer selection required for this command');
261
+ process.exit(1);
262
+ }
214
263
  // Get access token for the selected customer
215
264
  const accessToken = await getValidAccessToken(selectedCustomer);
216
265
  const client = await makeClient(verbose, accessToken);
217
- if (cmd === 'pull') {
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);
221
- }
222
- else if (cmd === 'push') {
266
+ if (cmd === 'push') {
223
267
  await pushChanged(client, selectedCustomer, verbose);
224
268
  }
225
269
  else if (cmd === 'status') {
@@ -15,6 +15,14 @@ export declare function getCustomer(config: MultiCustomerConfig, customerIdn: st
15
15
  * Get default customer or throw error if none
16
16
  */
17
17
  export declare function getDefaultCustomer(config: MultiCustomerConfig): CustomerConfig;
18
+ /**
19
+ * Attempt to get default customer, return null if multiple customers exist without default
20
+ */
21
+ export declare function tryGetDefaultCustomer(config: MultiCustomerConfig): CustomerConfig | null;
22
+ /**
23
+ * Get all customers as an array
24
+ */
25
+ export declare function getAllCustomers(config: MultiCustomerConfig): CustomerConfig[];
18
26
  /**
19
27
  * Validate customer configuration
20
28
  */
@@ -49,6 +49,34 @@ export function getDefaultCustomer(config) {
49
49
  throw new Error(`Multiple customers configured but no default specified. Available: ${customerIdns.join(', ')}. ` +
50
50
  `Set NEWO_DEFAULT_CUSTOMER or use --customer flag.`);
51
51
  }
52
+ /**
53
+ * Attempt to get default customer, return null if multiple customers exist without default
54
+ */
55
+ export function tryGetDefaultCustomer(config) {
56
+ if (config.defaultCustomer) {
57
+ const customer = getCustomer(config, config.defaultCustomer);
58
+ if (customer)
59
+ return customer;
60
+ }
61
+ const customerIdns = listCustomers(config);
62
+ if (customerIdns.length === 1) {
63
+ const firstCustomerIdn = customerIdns[0];
64
+ if (firstCustomerIdn) {
65
+ return config.customers[firstCustomerIdn];
66
+ }
67
+ }
68
+ if (customerIdns.length === 0) {
69
+ throw new Error('No customers configured. Please set NEWO_API_KEYS or NEWO_CUSTOMER_[IDN]_API_KEY in your .env file.');
70
+ }
71
+ // Return null if multiple customers exist without default (don't throw)
72
+ return null;
73
+ }
74
+ /**
75
+ * Get all customers as an array
76
+ */
77
+ export function getAllCustomers(config) {
78
+ return listCustomers(config).map(idn => getCustomer(config, idn));
79
+ }
52
80
  /**
53
81
  * Validate customer configuration
54
82
  */
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "newo",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "description": "NEWO CLI: sync AI Agent skills between NEWO platform and local files. Multi-customer workspaces, Git-first workflows, comprehensive project management.",
5
+ "type": "module",
5
6
  "bin": {
6
7
  "newo": "dist/cli.js"
7
8
  },
package/src/cli.ts CHANGED
@@ -5,7 +5,7 @@ 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
7
  import { initializeEnvironment, ENV, EnvValidationError } from './env.js';
8
- import { parseCustomerConfigAsync, listCustomers, getCustomer, getDefaultCustomer, validateCustomerConfig } from './customerAsync.js';
8
+ import { parseCustomerConfigAsync, listCustomers, getCustomer, getDefaultCustomer, tryGetDefaultCustomer, getAllCustomers, validateCustomerConfig } from './customerAsync.js';
9
9
  import { getValidAccessToken } from './auth.js';
10
10
  import path from 'path';
11
11
  import type { CliArgs, NewoApiError, CustomerConfig } from './types.js';
@@ -131,7 +131,7 @@ async function main(): Promise<void> {
131
131
  const cmd = args._[0];
132
132
  const verbose = Boolean(args.verbose || args.v);
133
133
 
134
- // Parse customer configuration (async for API key array support)
134
+ // Parse customer configuration (async for API key array support)
135
135
  let customerConfig;
136
136
  try {
137
137
  customerConfig = await parseCustomerConfigAsync(ENV as any, verbose);
@@ -145,8 +145,9 @@ async function main(): Promise<void> {
145
145
  }
146
146
 
147
147
  // Handle customer selection
148
- let selectedCustomer: CustomerConfig;
149
-
148
+ let selectedCustomer: CustomerConfig | null = null;
149
+ let allCustomers: CustomerConfig[] = [];
150
+
150
151
  if (cmd === 'list-customers') {
151
152
  const customers = listCustomers(customerConfig);
152
153
  console.log('Available customers:');
@@ -156,7 +157,7 @@ async function main(): Promise<void> {
156
157
  }
157
158
  return;
158
159
  }
159
-
160
+
160
161
  if (args.customer) {
161
162
  const customer = getCustomer(customerConfig, args.customer as string);
162
163
  if (!customer) {
@@ -166,12 +167,29 @@ async function main(): Promise<void> {
166
167
  }
167
168
  selectedCustomer = customer;
168
169
  } else {
169
- try {
170
- selectedCustomer = getDefaultCustomer(customerConfig);
171
- } catch (error: unknown) {
172
- const message = error instanceof Error ? error.message : String(error);
173
- console.error(message);
174
- process.exit(1);
170
+ // For pull command, try to get default but fall back to all customers if multiple exist
171
+ if (cmd === 'pull') {
172
+ try {
173
+ selectedCustomer = tryGetDefaultCustomer(customerConfig);
174
+ if (!selectedCustomer) {
175
+ // Multiple customers exist with no default, pull from all
176
+ allCustomers = getAllCustomers(customerConfig);
177
+ if (verbose) console.log(`📥 No default customer specified, pulling from all ${allCustomers.length} customers`);
178
+ }
179
+ } catch (error: unknown) {
180
+ const message = error instanceof Error ? error.message : String(error);
181
+ console.error(message);
182
+ process.exit(1);
183
+ }
184
+ } else {
185
+ // For other commands, require explicit customer selection
186
+ try {
187
+ selectedCustomer = getDefaultCustomer(customerConfig);
188
+ } catch (error: unknown) {
189
+ const message = error instanceof Error ? error.message : String(error);
190
+ console.error(message);
191
+ process.exit(1);
192
+ }
175
193
  }
176
194
  }
177
195
 
@@ -184,28 +202,29 @@ Usage:
184
202
  newo list-customers # list available customers
185
203
  newo meta [--customer <idn>] # get project metadata (debug)
186
204
  newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles from file
187
-
205
+
188
206
  Flags:
189
- --customer <idn> # specify customer (if not set, uses default)
207
+ --customer <idn> # specify customer (if not set, uses default or all for pull)
190
208
  --verbose, -v # enable detailed logging
191
-
209
+
192
210
  Environment Variables:
193
211
  NEWO_BASE_URL # NEWO API base URL (default: https://app.newo.ai)
194
212
  NEWO_CUSTOMER_<IDN>_API_KEY # API key for customer <IDN>
195
213
  NEWO_CUSTOMER_<IDN>_PROJECT_ID # Optional: specific project ID for customer
196
214
  NEWO_DEFAULT_CUSTOMER # Optional: default customer to use
197
-
215
+
198
216
  Multi-Customer Examples:
199
217
  # Configure customers in .env:
200
218
  NEWO_CUSTOMER_acme_API_KEY=your_acme_api_key
201
219
  NEWO_CUSTOMER_globex_API_KEY=your_globex_api_key
202
220
  NEWO_DEFAULT_CUSTOMER=acme
203
-
221
+
204
222
  # Commands:
205
- newo pull --customer acme # Pull projects for Acme
223
+ newo pull # Pull from all customers (if no default set)
224
+ newo pull --customer acme # Pull projects for Acme only
206
225
  newo push --customer globex # Push changes for Globex
207
226
  newo status # Status for default customer
208
-
227
+
209
228
  File Structure:
210
229
  newo_customers/
211
230
  ├── acme/
@@ -218,15 +237,39 @@ File Structure:
218
237
  return;
219
238
  }
220
239
 
240
+ if (cmd === 'pull') {
241
+ if (selectedCustomer) {
242
+ // Single customer pull
243
+ const accessToken = await getValidAccessToken(selectedCustomer);
244
+ const client = await makeClient(verbose, accessToken);
245
+ const projectId = selectedCustomer.projectId || null;
246
+ await pullAll(client, selectedCustomer, projectId, verbose);
247
+ } else if (allCustomers.length > 0) {
248
+ // Multi-customer pull
249
+ console.log(`🔄 Pulling from ${allCustomers.length} customers...`);
250
+ for (const customer of allCustomers) {
251
+ console.log(`\n📥 Pulling from customer: ${customer.idn}`);
252
+ const accessToken = await getValidAccessToken(customer);
253
+ const client = await makeClient(verbose, accessToken);
254
+ const projectId = customer.projectId || null;
255
+ await pullAll(client, customer, projectId, verbose);
256
+ }
257
+ console.log(`\n✅ Pull completed for all ${allCustomers.length} customers`);
258
+ }
259
+ return;
260
+ }
261
+
262
+ // For all other commands, require a single selected customer
263
+ if (!selectedCustomer) {
264
+ console.error('Customer selection required for this command');
265
+ process.exit(1);
266
+ }
267
+
221
268
  // Get access token for the selected customer
222
269
  const accessToken = await getValidAccessToken(selectedCustomer);
223
270
  const client = await makeClient(verbose, accessToken);
224
271
 
225
- if (cmd === 'pull') {
226
- // Use customer-specific project ID if set, otherwise pull all projects
227
- const projectId = selectedCustomer.projectId || null;
228
- await pullAll(client, selectedCustomer, projectId, verbose);
229
- } else if (cmd === 'push') {
272
+ if (cmd === 'push') {
230
273
  await pushChanged(client, selectedCustomer, verbose);
231
274
  } else if (cmd === 'status') {
232
275
  await status(selectedCustomer, verbose);
@@ -40,7 +40,7 @@ export function getDefaultCustomer(config: MultiCustomerConfig): CustomerConfig
40
40
  const customer = getCustomer(config, config.defaultCustomer);
41
41
  if (customer) return customer;
42
42
  }
43
-
43
+
44
44
  const customerIdns = listCustomers(config);
45
45
  if (customerIdns.length === 1) {
46
46
  const firstCustomerIdn = customerIdns[0];
@@ -48,17 +48,49 @@ export function getDefaultCustomer(config: MultiCustomerConfig): CustomerConfig
48
48
  return config.customers[firstCustomerIdn]!;
49
49
  }
50
50
  }
51
-
51
+
52
52
  if (customerIdns.length === 0) {
53
53
  throw new Error('No customers configured. Please set NEWO_API_KEYS or NEWO_CUSTOMER_[IDN]_API_KEY in your .env file.');
54
54
  }
55
-
55
+
56
56
  throw new Error(
57
57
  `Multiple customers configured but no default specified. Available: ${customerIdns.join(', ')}. ` +
58
58
  `Set NEWO_DEFAULT_CUSTOMER or use --customer flag.`
59
59
  );
60
60
  }
61
61
 
62
+ /**
63
+ * Attempt to get default customer, return null if multiple customers exist without default
64
+ */
65
+ export function tryGetDefaultCustomer(config: MultiCustomerConfig): CustomerConfig | null {
66
+ if (config.defaultCustomer) {
67
+ const customer = getCustomer(config, config.defaultCustomer);
68
+ if (customer) return customer;
69
+ }
70
+
71
+ const customerIdns = listCustomers(config);
72
+ if (customerIdns.length === 1) {
73
+ const firstCustomerIdn = customerIdns[0];
74
+ if (firstCustomerIdn) {
75
+ return config.customers[firstCustomerIdn]!;
76
+ }
77
+ }
78
+
79
+ if (customerIdns.length === 0) {
80
+ throw new Error('No customers configured. Please set NEWO_API_KEYS or NEWO_CUSTOMER_[IDN]_API_KEY in your .env file.');
81
+ }
82
+
83
+ // Return null if multiple customers exist without default (don't throw)
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Get all customers as an array
89
+ */
90
+ export function getAllCustomers(config: MultiCustomerConfig): CustomerConfig[] {
91
+ return listCustomers(config).map(idn => getCustomer(config, idn)!);
92
+ }
93
+
62
94
  /**
63
95
  * Validate customer configuration
64
96
  */