newo 3.4.2 → 3.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.
Files changed (64) hide show
  1. package/.env.example +5 -0
  2. package/CHANGELOG.md +21 -0
  3. package/dist/api.d.ts +18 -0
  4. package/dist/api.js +28 -0
  5. package/dist/cli/commands/export.d.ts +3 -0
  6. package/dist/cli/commands/export.js +62 -0
  7. package/dist/cli/commands/help.js +54 -42
  8. package/dist/cli/commands/pull.js +38 -14
  9. package/dist/cli/commands/push.js +32 -32
  10. package/dist/cli/commands/status.js +46 -7
  11. package/dist/cli-new/bootstrap.d.ts +7 -1
  12. package/dist/cli-new/bootstrap.js +11 -5
  13. package/dist/cli-new/di/tokens.d.ts +1 -0
  14. package/dist/cli-new/di/tokens.js +1 -0
  15. package/dist/cli.js +4 -0
  16. package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +5 -0
  17. package/dist/domain/strategies/sync/ProjectSyncStrategy.js +97 -8
  18. package/dist/domain/strategies/sync/V2ProjectSyncStrategy.d.ts +80 -0
  19. package/dist/domain/strategies/sync/V2ProjectSyncStrategy.js +725 -0
  20. package/dist/env.d.ts +1 -0
  21. package/dist/env.js +1 -0
  22. package/dist/format/detect.d.ts +14 -0
  23. package/dist/format/detect.js +105 -0
  24. package/dist/format/extensions.d.ts +26 -0
  25. package/dist/format/extensions.js +45 -0
  26. package/dist/format/index.d.ts +11 -0
  27. package/dist/format/index.js +11 -0
  28. package/dist/format/paths-v2.d.ts +31 -0
  29. package/dist/format/paths-v2.js +104 -0
  30. package/dist/format/types.d.ts +28 -0
  31. package/dist/format/types.js +21 -0
  32. package/dist/format/v2-yaml.d.ts +143 -0
  33. package/dist/format/v2-yaml.js +222 -0
  34. package/dist/format/yaml-patch.d.ts +14 -0
  35. package/dist/format/yaml-patch.js +184 -0
  36. package/dist/fsutil.d.ts +10 -0
  37. package/dist/fsutil.js +25 -0
  38. package/dist/sync/attributes.js +3 -3
  39. package/dist/sync/skill-files.js +2 -2
  40. package/dist/types.d.ts +5 -0
  41. package/package.json +1 -1
  42. package/src/api.ts +64 -0
  43. package/src/cli/commands/export.ts +78 -0
  44. package/src/cli/commands/help.ts +54 -42
  45. package/src/cli/commands/pull.ts +46 -15
  46. package/src/cli/commands/push.ts +38 -31
  47. package/src/cli/commands/status.ts +59 -9
  48. package/src/cli-new/bootstrap.ts +19 -7
  49. package/src/cli-new/di/tokens.ts +1 -0
  50. package/src/cli.ts +5 -0
  51. package/src/domain/strategies/sync/ProjectSyncStrategy.ts +122 -8
  52. package/src/domain/strategies/sync/V2ProjectSyncStrategy.ts +1007 -0
  53. package/src/env.ts +2 -0
  54. package/src/format/detect.ts +123 -0
  55. package/src/format/extensions.ts +61 -0
  56. package/src/format/index.ts +66 -0
  57. package/src/format/paths-v2.ts +207 -0
  58. package/src/format/types.ts +40 -0
  59. package/src/format/v2-yaml.ts +345 -0
  60. package/src/format/yaml-patch.ts +208 -0
  61. package/src/fsutil.ts +37 -0
  62. package/src/sync/attributes.ts +3 -3
  63. package/src/sync/skill-files.ts +2 -2
  64. package/src/types.ts +6 -0
package/.env.example CHANGED
@@ -19,6 +19,11 @@ NEWO_API_KEYS=["api_key_1", "api_key_2", "api_key_3"]
19
19
  # NEWO_API_KEY=put_api_key_here
20
20
  # NEWO_PROJECT_ID=b78188ba-0df0-46a8-8713-f0d7cff0a06e
21
21
 
22
+ # Project format (default: cli_v1, only affects NEW pulls where no local files exist)
23
+ # cli_v1 = Native CLI format (full feature support, per-entity metadata)
24
+ # newo_v2 = NEWO platform export format (compatible with platform UI exports)
25
+ # NEWO_FORMAT=cli_v1
26
+
22
27
  # Optional bootstrap tokens (for legacy mode or manual setup)
23
28
  NEWO_ACCESS_TOKEN=
24
29
  NEWO_REFRESH_TOKEN=
package/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [3.6.0] - 2026-04-13
11
+
12
+ ### Added
13
+
14
+ - **NEWO V2 Format Support** - Dual format support: `cli_v1` (native, default) and `newo_v2` (NEWO platform import/export compatible). Format is auto-detected per customer from filesystem (`import_version.txt` = newo_v2, `projects/` dir = cli_v1). Multiple formats coexist in the same workspace.
15
+ - **Libraries** - New entity type for shared reusable skills across agents within a project. Full pull/push/status support in both cli_v1 and newo_v2 formats via `GET/POST /api/v1/designer/projects/{id}/libraries` and `PATCH /api/v1/designer/libraries/{id}/skills/{id}`.
16
+ - **`newo export` command** - Bulk download V2 export ZIP from NEWO platform via `POST /api/v2/designer/customer/export`. Usage: `newo export [--output file.zip] [--customer idn]`.
17
+ - **`--format` flag** - Per-command format override for `pull`, `push`, and `status` commands. Values: `cli_v1`, `newo_v2`.
18
+ - **`NEWO_FORMAT` environment variable** - Persistent default format for new pulls (only affects customers without existing local files).
19
+ - **Format auto-detection** - Automatic per-customer format detection from filesystem markers. Existing projects always auto-detect; env var only applies to fresh pulls.
20
+ - **V2ProjectSyncStrategy** - New sync strategy using same V1 APIs but writing newo_v2 file layout (agents/flows/skills structure, .nsl/.nslg extensions, inline flow YAML with skills/events/states).
21
+ - **Format module** (`src/format/`) - Types, detection, extension mapping, V2 paths, V2 YAML parsers/generators, and pyyaml-compatible formatting.
22
+
23
+ ### Changed
24
+
25
+ - `pull`, `push`, and `status` commands now support `--format` flag and auto-detect format per customer
26
+ - DI bootstrap conditionally registers V2ProjectSyncStrategy when format is newo_v2
27
+ - `skill-files.ts` now recognizes `.nslg` extension alongside `.guidance`, `.jinja`, `.nsl`
28
+ - `env.ts` validates `NEWO_FORMAT` environment variable
29
+ - API client factory passes customer config to `getValidAccessToken()` for correct multi-customer auth
30
+
10
31
  ## [3.4.2] - 2026-04-12
11
32
 
12
33
  ### Added
package/dist/api.d.ts CHANGED
@@ -90,4 +90,22 @@ export declare function listRegistryItems(client: AxiosInstance, registryId: str
90
90
  export declare function addProjectFromRegistry(client: AxiosInstance, projectData: AddProjectFromRegistryRequest): Promise<CreateProjectResponse>;
91
91
  export declare function createNewoCustomer(client: AxiosInstance, customerData: CreateNewoCustomerRequest): Promise<CreateNewoCustomerResponse>;
92
92
  export declare function getLogs(client: AxiosInstance, params: LogsQueryParams): Promise<LogsResponse>;
93
+ export interface LibraryResponse {
94
+ id: string;
95
+ idn: string;
96
+ project_id: string;
97
+ skills: Skill[];
98
+ }
99
+ export declare function listLibraries(client: AxiosInstance, projectId: string): Promise<LibraryResponse[]>;
100
+ export declare function listLibrarySkills(client: AxiosInstance, libraryId: string): Promise<Skill[]>;
101
+ export declare function updateLibrarySkill(client: AxiosInstance, libraryId: string, skillId: string, data: {
102
+ prompt_script: string;
103
+ [key: string]: unknown;
104
+ }): Promise<void>;
105
+ export interface V2ExportOptions {
106
+ export_akb?: boolean;
107
+ export_customer_attributes?: boolean;
108
+ exclude_projects_idn?: string[];
109
+ }
110
+ export declare function exportCustomerV2(client: AxiosInstance, customerId: string, options?: V2ExportOptions): Promise<Buffer>;
93
111
  //# sourceMappingURL=api.d.ts.map
package/dist/api.js CHANGED
@@ -418,4 +418,32 @@ export async function getLogs(client, params) {
418
418
  const response = await client.get(`/api/v1/analytics/logs?${queryParams.toString()}`);
419
419
  return response.data;
420
420
  }
421
+ export async function listLibraries(client, projectId) {
422
+ const response = await client.get(`/api/v1/designer/projects/${projectId}/libraries`);
423
+ return response.data;
424
+ }
425
+ export async function listLibrarySkills(client, libraryId) {
426
+ const response = await client.get(`/api/v1/designer/libraries/${libraryId}/skills`);
427
+ return response.data;
428
+ }
429
+ export async function updateLibrarySkill(client, libraryId, skillId, data) {
430
+ await client.patch(`/api/v1/designer/libraries/${libraryId}/skills/${skillId}`, data);
431
+ }
432
+ export async function exportCustomerV2(client, customerId, options = {}) {
433
+ const params = new URLSearchParams();
434
+ params.set('customer_id', customerId);
435
+ if (options.export_akb !== undefined)
436
+ params.set('export_akb', String(options.export_akb));
437
+ if (options.export_customer_attributes !== undefined)
438
+ params.set('export_customer_attributes', String(options.export_customer_attributes));
439
+ if (options.exclude_projects_idn && options.exclude_projects_idn.length > 0) {
440
+ for (const idn of options.exclude_projects_idn) {
441
+ params.append('exclude_projects_idn', idn);
442
+ }
443
+ }
444
+ const response = await client.post(`/api/v2/designer/customer/export?${params.toString()}`, null, {
445
+ responseType: 'arraybuffer',
446
+ });
447
+ return Buffer.from(response.data);
448
+ }
421
449
  //# sourceMappingURL=api.js.map
@@ -0,0 +1,3 @@
1
+ import type { MultiCustomerConfig, CliArgs } from '../../types.js';
2
+ export declare function handleExportCommand(customerConfig: MultiCustomerConfig, args: CliArgs, verbose: boolean): Promise<void>;
3
+ //# sourceMappingURL=export.d.ts.map
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Export command handler - downloads V2 bulk export ZIP from platform
3
+ *
4
+ * Usage:
5
+ * newo export # Export to temp/export-{customerIdn}-{timestamp}.zip
6
+ * newo export --output my-export.zip # Export to specific file
7
+ * newo export --no-akb # Exclude AKB from export
8
+ * newo export --no-attributes # Exclude customer attributes
9
+ */
10
+ import { makeClient, exportCustomerV2 } from '../../api.js';
11
+ import { getValidAccessToken } from '../../auth.js';
12
+ import { selectSingleCustomer } from '../customer-selection.js';
13
+ import fs from 'fs-extra';
14
+ import path from 'path';
15
+ export async function handleExportCommand(customerConfig, args, verbose) {
16
+ const { selectedCustomer } = selectSingleCustomer(customerConfig, args.customer);
17
+ if (!selectedCustomer) {
18
+ console.error('Please specify a customer with --customer');
19
+ process.exit(1);
20
+ }
21
+ const accessToken = await getValidAccessToken(selectedCustomer);
22
+ const client = await makeClient(verbose, accessToken);
23
+ // Get customer ID from token (needed for V2 export API)
24
+ const customerId = await getCustomerIdFromToken(accessToken);
25
+ if (!customerId) {
26
+ console.error('Could not determine customer ID from token');
27
+ process.exit(1);
28
+ }
29
+ const exportAkb = !args['no-akb'];
30
+ const exportAttributes = !args['no-attributes'];
31
+ console.log(`Downloading V2 export for customer ${selectedCustomer.idn}...`);
32
+ const zipBuffer = await exportCustomerV2(client, customerId, {
33
+ export_akb: exportAkb,
34
+ export_customer_attributes: exportAttributes,
35
+ });
36
+ // Determine output path
37
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
38
+ const defaultName = `export-${selectedCustomer.idn}-${timestamp}.zip`;
39
+ const outputPath = args.output || args.o
40
+ || path.join(process.cwd(), 'temp', defaultName);
41
+ await fs.ensureDir(path.dirname(outputPath));
42
+ await fs.writeFile(outputPath, zipBuffer);
43
+ console.log(`Exported ${(zipBuffer.length / 1024 / 1024).toFixed(1)}MB to ${outputPath}`);
44
+ }
45
+ /**
46
+ * Extract customer_id from JWT token payload
47
+ */
48
+ function getCustomerIdFromToken(token) {
49
+ try {
50
+ const parts = token.split('.');
51
+ if (parts.length < 2)
52
+ return null;
53
+ const payload = parts[1];
54
+ const padded = payload + '='.repeat(4 - (payload.length % 4));
55
+ const decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
56
+ return decoded['customer_id'] || decoded['sub'] || null;
57
+ }
58
+ catch {
59
+ return null;
60
+ }
61
+ }
62
+ //# sourceMappingURL=export.js.map
@@ -6,19 +6,20 @@ export function handleHelpCommand() {
6
6
  A professional command-line tool for NEWO AI Agent development with modular architecture and comprehensive multi-customer support.
7
7
 
8
8
  Core Commands:
9
- newo pull [--customer <idn>] # download projects + attributes -> ./newo_customers/<idn>/
10
- newo push [--customer <idn>] [--no-publish] # upload modified *.guidance/*.jinja + attributes back to NEWO, publish flows by default
11
- newo status [--customer <idn>] # show modified files that would be pushed
12
- newo watch [--customer <idn>] # watch for file changes and auto-push (NEW)
13
- newo diff [--customer <idn>] # show differences between local and remote (NEW)
14
- newo logs [--customer <idn>] # fetch and display analytics logs from platform (NEW)
15
- newo conversations [--customer <idn>] [--all] # download user conversations -> ./newo_customers/<idn>/conversations.yaml
9
+ newo pull [--customer <idn>] [--format <fmt>] # download projects + attributes + libraries
10
+ newo push [--customer <idn>] [--format <fmt>] # upload modified skills + attributes, publish flows
11
+ newo status [--customer <idn>] [--format <fmt>] # show modified files that would be pushed
12
+ newo export [--customer <idn>] [--output <file>] # download V2 bulk export ZIP from platform
13
+ newo watch [--customer <idn>] # watch for file changes and auto-push
14
+ newo diff [--customer <idn>] # show differences between local and remote
15
+ newo logs [--customer <idn>] # fetch and display analytics logs from platform
16
+ newo conversations [--customer <idn>] [--all] # download user conversations -> conversations.yaml
16
17
  newo sandbox "<message>" [--customer <idn>] # test agent in sandbox - single message mode
17
- newo sandbox --actor <id> "message" # continue existing sandbox conversation with chat ID
18
+ newo sandbox --actor <id> "message" # continue existing sandbox conversation
18
19
  newo pull-attributes [--customer <idn>] # download customer + project attributes
19
- newo list-customers # list available customers and their configuration
20
- newo meta [--customer <idn>] # get project metadata (debug command)
21
- newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles from structured text file
20
+ newo list-customers # list available customers
21
+ newo meta [--customer <idn>] # get project metadata (debug)
22
+ newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles
22
23
 
23
24
  Project Management:
24
25
  newo create-project <idn> [--title <title>] [--description <desc>] [--version <version>] [--auto-update] # create empty project on platform
@@ -74,6 +75,7 @@ Account & Customer Management:
74
75
 
75
76
  Flags:
76
77
  --customer <idn> # specify customer (if not set, uses default or interactive selection)
78
+ --format <cli_v1|newo_v2> # project format (auto-detected from filesystem, or set NEWO_FORMAT in .env)
77
79
  --all # include all available data (for conversations: all personas and acts)
78
80
  --force, -f # force overwrite without prompting (for pull command)
79
81
  --verbose, -v # enable detailed logging and progress information
@@ -81,6 +83,7 @@ Flags:
81
83
  --actor <id> # continue existing sandbox chat with actor/chat ID
82
84
  --confirm # confirm destructive operations without prompting
83
85
  --no-publish # skip automatic flow publishing during push operations
86
+ --output, -o <file> # output file path (for export command)
84
87
 
85
88
  Selective Sync Flags (NEW):
86
89
  --only <resources> # sync only specified resources (comma-separated)
@@ -98,6 +101,7 @@ Selective Sync Flags (NEW):
98
101
 
99
102
  Environment Variables:
100
103
  NEWO_BASE_URL # NEWO API base URL (default: https://app.newo.ai)
104
+ NEWO_FORMAT # Default format for new pulls: cli_v1 (default) or newo_v2
101
105
 
102
106
  Single Customer:
103
107
  NEWO_API_KEY # API key for single customer setup
@@ -199,37 +203,45 @@ Usage Examples:
199
203
  newo create-customer "Test Co" --email test@test.com --status temporal # Temporal (trial) customer
200
204
  newo create-customer "Partner" --email p@p.com --project nac_integration --auto-update # With project template
201
205
 
202
- File Structure:
203
- newo_customers/
204
- ā”œā”€ā”€ <customer-idn>/
205
- │ ā”œā”€ā”€ attributes.yaml # Customer attributes (pull-attributes)
206
- │ ā”œā”€ā”€ conversations.yaml # User conversations and personas
207
- │ ā”œā”€ā”€ akb/ # AKB knowledge base articles (pull-akb)
208
- │ │ └── <agent-idn>.yaml # AKB articles per agent persona
209
- │ ā”œā”€ā”€ integrations/ # Integration configurations (pull-integrations)
210
- │ │ ā”œā”€ā”€ integrations.yaml # Master integrations list
211
- │ │ └── <integration-idn>/
212
- │ │ ā”œā”€ā”€ <integration-idn>.yaml # Integration metadata + settings (combined)
213
- │ │ └── connectors/
214
- │ │ └── <connector-idn>/ # Each connector in own directory
215
- │ │ ā”œā”€ā”€ <connector-idn>.yaml # Connector config
216
- │ │ └── webhooks/ # Webhooks subdirectory (if any)
217
- │ │ ā”œā”€ā”€ outgoing.yaml # Outgoing webhooks
218
- │ │ └── incoming.yaml # Incoming webhooks
219
- │ └── projects/
220
- │ └── <project-idn>/
221
- │ ā”œā”€ā”€ attributes.yaml # Project attributes (pull-attributes)
222
- │ ā”œā”€ā”€ flows.yaml # Auto-generated project structure
223
- │ ā”œā”€ā”€ metadata.yaml # Project metadata
224
- │ └── <agent-idn>/
225
- │ ā”œā”€ā”€ metadata.yaml # Agent metadata
226
- │ └── <flow-idn>/
227
- │ ā”œā”€ā”€ metadata.yaml # Flow metadata
228
- │ └── <skill-idn>/
229
- │ ā”œā”€ā”€ skill.guidance # AI guidance scripts
230
- │ ā”œā”€ā”€ skill.jinja # NSL/Jinja template scripts
231
- │ └── metadata.yaml # Skill metadata
232
- └── .newo/ # CLI state and mappings (auto-generated)
206
+ File Structure (cli_v1 - default):
207
+ newo_customers/<customer-idn>/
208
+ ā”œā”€ā”€ attributes.yaml # Customer attributes
209
+ ā”œā”€ā”€ conversations.yaml # User conversations
210
+ ā”œā”€ā”€ akb/<agent-idn>.yaml # AKB knowledge base
211
+ ā”œā”€ā”€ integrations/ # Integration configurations
212
+ └── projects/<project-idn>/
213
+ ā”œā”€ā”€ metadata.yaml # Project metadata
214
+ ā”œā”€ā”€ flows.yaml # Auto-generated structure
215
+ ā”œā”€ā”€ attributes.yaml # Project attributes
216
+ ā”œā”€ā”€ libraries/<lib-idn>/ # Shared skill libraries
217
+ │ ā”œā”€ā”€ metadata.yaml
218
+ │ └── <skill-idn>/<skill>.jinja|.guidance
219
+ └── <agent-idn>/<flow-idn>/<skill-idn>/
220
+ ā”œā”€ā”€ <skill>.guidance|.jinja # Skill scripts
221
+ └── metadata.yaml # Skill metadata
222
+
223
+ File Structure (newo_v2 - platform compatible):
224
+ newo_customers/<customer-idn>/
225
+ ā”œā”€ā”€ import_version.txt # Format marker (v2.0.0)
226
+ ā”œā”€ā”€ attributes.yaml # Customer attributes
227
+ ā”œā”€ā”€ akb/<agent-idn>.yaml # AKB knowledge base
228
+ └── <project-idn>/
229
+ ā”œā”€ā”€ <project-idn>.yaml # Project metadata
230
+ ā”œā”€ā”€ attributes.yaml # Project attributes
231
+ ā”œā”€ā”€ libraries/<lib-idn>/ # Shared skill libraries
232
+ │ ā”œā”€ā”€ <lib-idn>.yaml # Library + inline skill defs
233
+ │ └── skills/<skill>.nsl|.nslg
234
+ └── agents/<agent-idn>/
235
+ ā”œā”€ā”€ agent.yaml # Agent metadata
236
+ └── flows/<flow-idn>/
237
+ ā”œā”€ā”€ <flow-idn>.yaml # Flow + inline skill defs + events
238
+ └── skills/<skill>.nsl|.nslg # Skill scripts
239
+
240
+ Format Detection:
241
+ Format is auto-detected per customer from the filesystem:
242
+ - import_version.txt present -> newo_v2
243
+ - projects/ directory present -> cli_v1
244
+ Set NEWO_FORMAT=newo_v2 in .env for new pulls, or use --format flag
233
245
 
234
246
  For more information, visit: https://github.com/sabbah13/newo-cli
235
247
  `);
@@ -14,6 +14,7 @@ import { getValidAccessToken } from '../../auth.js';
14
14
  import { selectSingleCustomer } from '../customer-selection.js';
15
15
  import { setupCli } from '../../cli-new/bootstrap.js';
16
16
  import { ALL_RESOURCE_TYPES, RESOURCE_TYPES } from '../../cli-new/di/tokens.js';
17
+ import { resolveFormat } from '../../format/detect.js';
17
18
  /**
18
19
  * Parse resource list from comma-separated string
19
20
  */
@@ -40,10 +41,10 @@ function validateResources(resources) {
40
41
  return { valid, invalid };
41
42
  }
42
43
  /**
43
- * Pull using V2 SyncEngine with selective sync
44
+ * Pull using SyncEngine with selective sync and format support
44
45
  */
45
- async function pullWithV2Engine(customerConfig, customer, resources, verbose, silentOverwrite) {
46
- const { syncEngine, logger } = setupCli(customerConfig, verbose);
46
+ async function pullWithSyncEngine(customerConfig, customer, resources, verbose, silentOverwrite, formatVersion) {
47
+ const { syncEngine, logger } = setupCli(customerConfig, verbose, formatVersion);
47
48
  const pullOptions = {
48
49
  verbose,
49
50
  silentOverwrite
@@ -102,14 +103,27 @@ export async function handlePullCommand(customerConfig, args, verbose) {
102
103
  resourcesToFetch = 'all';
103
104
  console.log(`šŸ“¦ Pulling ALL resources`);
104
105
  }
105
- // Use V2 engine if selective sync requested, otherwise use legacy for backward compatibility
106
- const useV2Engine = onlyResources.length > 0 || excludeResources.length > 0 || pullAllResources;
106
+ // Use SyncEngine if selective sync requested, otherwise use legacy for backward compatibility
107
+ const useSyncEngine = onlyResources.length > 0 || excludeResources.length > 0 || pullAllResources;
108
+ // Explicit --format flag (applies to all customers in this run)
109
+ const explicitFormat = args.format;
107
110
  if (selectedCustomer) {
108
- if (useV2Engine) {
109
- await pullWithV2Engine(customerConfig, selectedCustomer, resourcesToFetch, verbose, silentOverwrite);
111
+ // Resolve format for this customer
112
+ const formatConfig = resolveFormat(selectedCustomer.idn, explicitFormat);
113
+ const isV2Format = formatConfig.version === 'newo_v2';
114
+ if (verbose || isV2Format) {
115
+ const sourceLabel = formatConfig.source === 'auto-detected' ? 'auto-detected'
116
+ : formatConfig.source === 'env-var' ? 'from .env'
117
+ : formatConfig.source === 'explicit-flag' ? '--format flag'
118
+ : 'default';
119
+ console.log(`Format: ${formatConfig.version} [${sourceLabel}]`);
120
+ }
121
+ // If format is newo_v2, always use SyncEngine (which will use V2ProjectSyncStrategy)
122
+ if (useSyncEngine || isV2Format) {
123
+ await pullWithSyncEngine(customerConfig, selectedCustomer, resourcesToFetch, verbose, silentOverwrite, formatConfig.version);
110
124
  }
111
125
  else {
112
- // Legacy behavior: pull projects + attributes only
126
+ // Legacy behavior: pull projects + attributes only (cli_v1)
113
127
  const accessToken = await getValidAccessToken(selectedCustomer);
114
128
  const client = await makeClient(verbose, accessToken);
115
129
  const projectId = selectedCustomer.projectId || null;
@@ -118,12 +132,22 @@ export async function handlePullCommand(customerConfig, args, verbose) {
118
132
  }
119
133
  else if (isMultiCustomer) {
120
134
  if (verbose)
121
- console.log(`šŸ“„ No default customer specified, pulling from all ${allCustomers.length} customers`);
122
- console.log(`šŸ”„ Pulling from ${allCustomers.length} customers...`);
135
+ console.log(`No default customer specified, pulling from all ${allCustomers.length} customers`);
136
+ console.log(`Pulling from ${allCustomers.length} customers...`);
123
137
  for (const customer of allCustomers) {
124
- console.log(`\nšŸ“„ Pulling from customer: ${customer.idn}`);
125
- if (useV2Engine) {
126
- await pullWithV2Engine(customerConfig, customer, resourcesToFetch, verbose, silentOverwrite);
138
+ console.log(`\nPulling from customer: ${customer.idn}`);
139
+ // Resolve format per customer (auto-detect from filesystem)
140
+ const formatConfig = resolveFormat(customer.idn, explicitFormat);
141
+ const isV2Format = formatConfig.version === 'newo_v2';
142
+ if (verbose || isV2Format) {
143
+ const sourceLabel = formatConfig.source === 'auto-detected' ? 'auto-detected'
144
+ : formatConfig.source === 'env-var' ? 'from .env'
145
+ : formatConfig.source === 'explicit-flag' ? '--format flag'
146
+ : 'default';
147
+ console.log(` Format: ${formatConfig.version} [${sourceLabel}]`);
148
+ }
149
+ if (useSyncEngine || isV2Format) {
150
+ await pullWithSyncEngine(customerConfig, customer, resourcesToFetch, verbose, silentOverwrite, formatConfig.version);
127
151
  }
128
152
  else {
129
153
  const accessToken = await getValidAccessToken(customer);
@@ -132,7 +156,7 @@ export async function handlePullCommand(customerConfig, args, verbose) {
132
156
  await pullAll(client, customer, projectId, verbose, silentOverwrite);
133
157
  }
134
158
  }
135
- console.log(`\nāœ… Pull completed for all ${allCustomers.length} customers`);
159
+ console.log(`\nPull completed for all ${allCustomers.length} customers`);
136
160
  }
137
161
  }
138
162
  //# sourceMappingURL=pull.js.map
@@ -15,6 +15,7 @@ import { getValidAccessToken } from '../../auth.js';
15
15
  import { selectSingleCustomer, interactiveCustomerSelection } from '../customer-selection.js';
16
16
  import { setupCli } from '../../cli-new/bootstrap.js';
17
17
  import { PUSHABLE_RESOURCE_TYPES } from '../../cli-new/di/tokens.js';
18
+ import { resolveFormat } from '../../format/detect.js';
18
19
  /**
19
20
  * Parse resource list from comma-separated string
20
21
  */
@@ -44,10 +45,10 @@ function validateResources(resources) {
44
45
  return { valid, invalid };
45
46
  }
46
47
  /**
47
- * Push using V2 SyncEngine with selective sync
48
+ * Push using SyncEngine with selective sync and format support
48
49
  */
49
- async function pushWithV2Engine(customerConfig, customer, resources, verbose) {
50
- const { syncEngine, logger } = setupCli(customerConfig, verbose);
50
+ async function pushWithSyncEngine(customerConfig, customer, resources, verbose, formatVersion) {
51
+ const { syncEngine, logger } = setupCli(customerConfig, verbose, formatVersion);
51
52
  if (resources === 'all') {
52
53
  const result = await syncEngine.pushAll(customer);
53
54
  logger.info(`āœ… Pushed: ${result.totalCreated} created, ${result.totalUpdated} updated, ${result.totalDeleted} deleted`);
@@ -103,47 +104,46 @@ export async function handlePushCommand(customerConfig, args, verbose) {
103
104
  resourcesToPush = 'all';
104
105
  console.log(`šŸ“¦ Pushing ALL resources`);
105
106
  }
106
- // Use V2 engine if selective sync requested, otherwise use legacy for backward compatibility
107
- const useV2Engine = onlyResources.length > 0 || excludeResources.length > 0 || pushAllResources;
108
- if (selectedCustomer) {
109
- if (useV2Engine) {
110
- await pushWithV2Engine(customerConfig, selectedCustomer, resourcesToPush, verbose);
107
+ // Use SyncEngine if selective sync requested, otherwise use legacy for backward compatibility
108
+ const useSyncEngine = onlyResources.length > 0 || excludeResources.length > 0 || pushAllResources;
109
+ const explicitFormat = args.format;
110
+ /**
111
+ * Push for a single customer with format detection
112
+ */
113
+ async function pushForCustomer(customer) {
114
+ const formatConfig = resolveFormat(customer.idn, explicitFormat);
115
+ const isV2Format = formatConfig.version === 'newo_v2';
116
+ if (verbose || isV2Format) {
117
+ const sourceLabel = formatConfig.source === 'auto-detected' ? 'auto-detected'
118
+ : formatConfig.source === 'env-var' ? 'from .env'
119
+ : formatConfig.source === 'explicit-flag' ? '--format flag'
120
+ : 'default';
121
+ console.log(`Format: ${formatConfig.version} [${sourceLabel}]`);
122
+ }
123
+ if (useSyncEngine || isV2Format) {
124
+ await pushWithSyncEngine(customerConfig, customer, resourcesToPush, verbose, formatConfig.version);
111
125
  }
112
126
  else {
113
- // Legacy behavior
114
- const accessToken = await getValidAccessToken(selectedCustomer);
127
+ const accessToken = await getValidAccessToken(customer);
115
128
  const client = await makeClient(verbose, accessToken);
116
- await pushChanged(client, selectedCustomer, verbose, shouldPublish);
129
+ await pushChanged(client, customer, verbose, shouldPublish);
117
130
  }
118
131
  }
132
+ if (selectedCustomer) {
133
+ await pushForCustomer(selectedCustomer);
134
+ }
119
135
  else if (isMultiCustomer) {
120
- // Multiple customers exist with no default, ask user
121
136
  const customersToProcess = await interactiveCustomerSelection(allCustomers);
122
137
  if (customersToProcess.length === 1) {
123
- const customer = customersToProcess[0];
124
- if (useV2Engine) {
125
- await pushWithV2Engine(customerConfig, customer, resourcesToPush, verbose);
126
- }
127
- else {
128
- const accessToken = await getValidAccessToken(customer);
129
- const client = await makeClient(verbose, accessToken);
130
- await pushChanged(client, customer, verbose, shouldPublish);
131
- }
138
+ await pushForCustomer(customersToProcess[0]);
132
139
  }
133
140
  else {
134
- console.log(`šŸ”„ Pushing to ${customersToProcess.length} customers...`);
141
+ console.log(`Pushing to ${customersToProcess.length} customers...`);
135
142
  for (const customer of customersToProcess) {
136
- console.log(`\nšŸ“¤ Pushing for customer: ${customer.idn}`);
137
- if (useV2Engine) {
138
- await pushWithV2Engine(customerConfig, customer, resourcesToPush, verbose);
139
- }
140
- else {
141
- const accessToken = await getValidAccessToken(customer);
142
- const client = await makeClient(verbose, accessToken);
143
- await pushChanged(client, customer, verbose, shouldPublish);
144
- }
143
+ console.log(`\nPushing for customer: ${customer.idn}`);
144
+ await pushForCustomer(customer);
145
145
  }
146
- console.log(`\nāœ… Push completed for all ${customersToProcess.length} customers`);
146
+ console.log(`\nPush completed for all ${customersToProcess.length} customers`);
147
147
  }
148
148
  }
149
149
  }
@@ -1,22 +1,61 @@
1
1
  /**
2
2
  * Status command handler
3
+ *
4
+ * Supports format-aware status with --format flag:
5
+ * newo status # auto-detect format per customer
6
+ * newo status --format newo_v2 # force V2 format
3
7
  */
4
8
  import { status } from '../../sync.js';
5
9
  import { selectSingleCustomer } from '../customer-selection.js';
10
+ import { setupCli } from '../../cli-new/bootstrap.js';
11
+ import { resolveFormat } from '../../format/detect.js';
12
+ async function statusForCustomer(customerConfig, customer, explicitFormat, verbose) {
13
+ const formatConfig = resolveFormat(customer.idn, explicitFormat);
14
+ const isV2Format = formatConfig.version === 'newo_v2';
15
+ if (verbose || isV2Format) {
16
+ const sourceLabel = formatConfig.source === 'auto-detected' ? 'auto-detected'
17
+ : formatConfig.source === 'env-var' ? 'from .env'
18
+ : formatConfig.source === 'explicit-flag' ? '--format flag'
19
+ : 'default';
20
+ console.log(`Format: ${formatConfig.version} [${sourceLabel}]`);
21
+ }
22
+ if (isV2Format) {
23
+ // Use SyncEngine with V2ProjectSyncStrategy
24
+ const { syncEngine, logger } = setupCli(customerConfig, verbose, formatConfig.version);
25
+ const report = await syncEngine.getStatus(customer);
26
+ logger.info(`\nStatus for customer: ${report.customer}`);
27
+ logger.info(`Total changes: ${report.totalChanges}\n`);
28
+ for (const resource of report.resources) {
29
+ if (resource.changedCount > 0) {
30
+ logger.info(`${resource.displayName}: ${resource.changedCount} change(s)`);
31
+ for (const change of resource.changes) {
32
+ const op = change.operation.toUpperCase().charAt(0);
33
+ logger.info(` ${op} ${change.path}`);
34
+ }
35
+ }
36
+ }
37
+ if (report.totalChanges === 0) {
38
+ logger.info('No changes to push.');
39
+ }
40
+ }
41
+ else {
42
+ // Legacy V1 status
43
+ await status(customer, verbose);
44
+ }
45
+ }
6
46
  export async function handleStatusCommand(customerConfig, args, verbose) {
7
47
  const { selectedCustomer, allCustomers, isMultiCustomer } = selectSingleCustomer(customerConfig, args.customer);
48
+ const explicitFormat = args.format;
8
49
  if (selectedCustomer) {
9
- // Single customer status
10
- await status(selectedCustomer, verbose);
50
+ await statusForCustomer(customerConfig, selectedCustomer, explicitFormat, verbose);
11
51
  }
12
52
  else if (isMultiCustomer) {
13
- // Multi-customer status
14
- console.log(`šŸ”„ Checking status for ${allCustomers.length} customers...`);
53
+ console.log(`Checking status for ${allCustomers.length} customers...`);
15
54
  for (const customer of allCustomers) {
16
- console.log(`\nšŸ“‹ Status for customer: ${customer.idn}`);
17
- await status(customer, verbose);
55
+ console.log(`\nStatus for customer: ${customer.idn}`);
56
+ await statusForCustomer(customerConfig, customer, explicitFormat, verbose);
18
57
  }
19
- console.log(`\nāœ… Status check completed for all ${allCustomers.length} customers`);
58
+ console.log(`\nStatus check completed for all ${allCustomers.length} customers`);
20
59
  }
21
60
  }
22
61
  //# sourceMappingURL=status.js.map
@@ -8,6 +8,7 @@ import { ServiceContainer } from './di/Container.js';
8
8
  import { type ILogger, type CustomerConfig, type MultiCustomerConfig } from '../domain/resources/common/types.js';
9
9
  import { SyncEngine, type SyncEngineOptions } from '../application/sync/SyncEngine.js';
10
10
  import { MigrationEngine } from '../application/migration/MigrationEngine.js';
11
+ import type { FormatVersion } from '../format/types.js';
11
12
  import type { AxiosInstance } from 'axios';
12
13
  /**
13
14
  * API Client Factory that creates authenticated clients
@@ -25,6 +26,11 @@ export interface BootstrapOptions {
25
26
  * Sync engine options
26
27
  */
27
28
  syncEngineOptions?: SyncEngineOptions;
29
+ /**
30
+ * Format version for project sync strategy
31
+ * 'newo_v2' uses V2ProjectSyncStrategy, 'cli_v1' (default) uses ProjectSyncStrategy
32
+ */
33
+ formatVersion?: FormatVersion | undefined;
28
34
  }
29
35
  /**
30
36
  * Create and configure the service container
@@ -47,7 +53,7 @@ export declare function getLogger(container: ServiceContainer): ILogger;
47
53
  *
48
54
  * Creates a configured container with all services ready to use.
49
55
  */
50
- export declare function setupCli(customerConfig: MultiCustomerConfig, verbose?: boolean): {
56
+ export declare function setupCli(customerConfig: MultiCustomerConfig, verbose?: boolean, formatVersion?: FormatVersion): {
51
57
  container: ServiceContainer;
52
58
  syncEngine: SyncEngine;
53
59
  migrationEngine: MigrationEngine;
@@ -10,6 +10,7 @@ import { ConsoleLogger } from '../domain/resources/common/types.js';
10
10
  import { SyncEngine } from '../application/sync/SyncEngine.js';
11
11
  import { MigrationEngine, TransformService } from '../application/migration/MigrationEngine.js';
12
12
  import { ProjectSyncStrategy, createProjectSyncStrategy } from '../domain/strategies/sync/ProjectSyncStrategy.js';
13
+ import { createV2ProjectSyncStrategy } from '../domain/strategies/sync/V2ProjectSyncStrategy.js';
13
14
  import { AttributeSyncStrategy, createAttributeSyncStrategy } from '../domain/strategies/sync/AttributeSyncStrategy.js';
14
15
  import { IntegrationSyncStrategy, createIntegrationSyncStrategy } from '../domain/strategies/sync/IntegrationSyncStrategy.js';
15
16
  import { AkbSyncStrategy, createAkbSyncStrategy } from '../domain/strategies/sync/AkbSyncStrategy.js';
@@ -25,7 +26,7 @@ export async function createApiClient(customer, verbose) {
25
26
  if (customer.projectId) {
26
27
  process.env.NEWO_PROJECT_ID = customer.projectId;
27
28
  }
28
- const token = await getValidAccessToken();
29
+ const token = await getValidAccessToken(customer);
29
30
  return makeClient(verbose, token);
30
31
  }
31
32
  /**
@@ -43,8 +44,13 @@ export function createServiceContainer(customerConfig, options = {}) {
43
44
  // API Client Factory
44
45
  container.registerValue(TOKENS.API_CLIENT_FACTORY, createApiClient);
45
46
  // === Domain Layer - Sync Strategies ===
46
- // Project Sync Strategy
47
- container.registerSingleton(TOKENS.PROJECT_SYNC_STRATEGY, () => createProjectSyncStrategy(createApiClient, container.get(TOKENS.LOGGER)));
47
+ // Project Sync Strategy (format-conditional: cli_v1 or newo_v2)
48
+ container.registerSingleton(TOKENS.PROJECT_SYNC_STRATEGY, () => {
49
+ if (options.formatVersion === 'newo_v2') {
50
+ return createV2ProjectSyncStrategy(createApiClient, container.get(TOKENS.LOGGER));
51
+ }
52
+ return createProjectSyncStrategy(createApiClient, container.get(TOKENS.LOGGER));
53
+ });
48
54
  // Attribute Sync Strategy
49
55
  container.registerSingleton(TOKENS.ATTRIBUTE_SYNC_STRATEGY, () => createAttributeSyncStrategy(createApiClient, container.get(TOKENS.LOGGER)));
50
56
  // Integration Sync Strategy
@@ -96,8 +102,8 @@ export function getLogger(container) {
96
102
  *
97
103
  * Creates a configured container with all services ready to use.
98
104
  */
99
- export function setupCli(customerConfig, verbose = false) {
100
- const container = createServiceContainer(customerConfig, { verbose });
105
+ export function setupCli(customerConfig, verbose = false, formatVersion) {
106
+ const container = createServiceContainer(customerConfig, { verbose, formatVersion });
101
107
  return {
102
108
  container,
103
109
  syncEngine: getSyncEngine(container),