newo 3.4.2 → 3.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.env.example +5 -0
  2. package/CHANGELOG.md +27 -0
  3. package/README.md +27 -9
  4. package/dist/api.d.ts +18 -0
  5. package/dist/api.js +28 -0
  6. package/dist/cli/commands/export.d.ts +3 -0
  7. package/dist/cli/commands/export.js +62 -0
  8. package/dist/cli/commands/help.js +54 -42
  9. package/dist/cli/commands/pull.js +38 -14
  10. package/dist/cli/commands/push.js +32 -32
  11. package/dist/cli/commands/status.js +46 -7
  12. package/dist/cli-new/bootstrap.d.ts +7 -1
  13. package/dist/cli-new/bootstrap.js +11 -5
  14. package/dist/cli-new/di/tokens.d.ts +1 -0
  15. package/dist/cli-new/di/tokens.js +1 -0
  16. package/dist/cli.js +4 -0
  17. package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +5 -0
  18. package/dist/domain/strategies/sync/ProjectSyncStrategy.js +97 -8
  19. package/dist/domain/strategies/sync/V2ProjectSyncStrategy.d.ts +80 -0
  20. package/dist/domain/strategies/sync/V2ProjectSyncStrategy.js +725 -0
  21. package/dist/env.d.ts +1 -0
  22. package/dist/env.js +1 -0
  23. package/dist/format/detect.d.ts +14 -0
  24. package/dist/format/detect.js +105 -0
  25. package/dist/format/extensions.d.ts +26 -0
  26. package/dist/format/extensions.js +45 -0
  27. package/dist/format/index.d.ts +11 -0
  28. package/dist/format/index.js +11 -0
  29. package/dist/format/paths-v2.d.ts +31 -0
  30. package/dist/format/paths-v2.js +104 -0
  31. package/dist/format/types.d.ts +28 -0
  32. package/dist/format/types.js +21 -0
  33. package/dist/format/v2-yaml.d.ts +143 -0
  34. package/dist/format/v2-yaml.js +222 -0
  35. package/dist/format/yaml-patch.d.ts +14 -0
  36. package/dist/format/yaml-patch.js +184 -0
  37. package/dist/fsutil.d.ts +10 -0
  38. package/dist/fsutil.js +25 -0
  39. package/dist/sync/attributes.js +3 -3
  40. package/dist/sync/skill-files.js +2 -2
  41. package/dist/types.d.ts +5 -0
  42. package/package.json +1 -1
  43. package/src/api.ts +64 -0
  44. package/src/cli/commands/export.ts +78 -0
  45. package/src/cli/commands/help.ts +54 -42
  46. package/src/cli/commands/pull.ts +46 -15
  47. package/src/cli/commands/push.ts +38 -31
  48. package/src/cli/commands/status.ts +59 -9
  49. package/src/cli-new/bootstrap.ts +19 -7
  50. package/src/cli-new/di/tokens.ts +1 -0
  51. package/src/cli.ts +5 -0
  52. package/src/domain/strategies/sync/ProjectSyncStrategy.ts +122 -8
  53. package/src/domain/strategies/sync/V2ProjectSyncStrategy.ts +1007 -0
  54. package/src/env.ts +2 -0
  55. package/src/format/detect.ts +123 -0
  56. package/src/format/extensions.ts +61 -0
  57. package/src/format/index.ts +66 -0
  58. package/src/format/paths-v2.ts +207 -0
  59. package/src/format/types.ts +40 -0
  60. package/src/format/v2-yaml.ts +345 -0
  61. package/src/format/yaml-patch.ts +208 -0
  62. package/src/fsutil.ts +37 -0
  63. package/src/sync/attributes.ts +3 -3
  64. package/src/sync/skill-files.ts +2 -2
  65. package/src/types.ts +6 -0
@@ -7,19 +7,20 @@ export function handleHelpCommand(): void {
7
7
  A professional command-line tool for NEWO AI Agent development with modular architecture and comprehensive multi-customer support.
8
8
 
9
9
  Core Commands:
10
- newo pull [--customer <idn>] # download projects + attributes -> ./newo_customers/<idn>/
11
- newo push [--customer <idn>] [--no-publish] # upload modified *.guidance/*.jinja + attributes back to NEWO, publish flows by default
12
- newo status [--customer <idn>] # show modified files that would be pushed
13
- newo watch [--customer <idn>] # watch for file changes and auto-push (NEW)
14
- newo diff [--customer <idn>] # show differences between local and remote (NEW)
15
- newo logs [--customer <idn>] # fetch and display analytics logs from platform (NEW)
16
- newo conversations [--customer <idn>] [--all] # download user conversations -> ./newo_customers/<idn>/conversations.yaml
10
+ newo pull [--customer <idn>] [--format <fmt>] # download projects + attributes + libraries
11
+ newo push [--customer <idn>] [--format <fmt>] # upload modified skills + attributes, publish flows
12
+ newo status [--customer <idn>] [--format <fmt>] # show modified files that would be pushed
13
+ newo export [--customer <idn>] [--output <file>] # download V2 bulk export ZIP from platform
14
+ newo watch [--customer <idn>] # watch for file changes and auto-push
15
+ newo diff [--customer <idn>] # show differences between local and remote
16
+ newo logs [--customer <idn>] # fetch and display analytics logs from platform
17
+ newo conversations [--customer <idn>] [--all] # download user conversations -> conversations.yaml
17
18
  newo sandbox "<message>" [--customer <idn>] # test agent in sandbox - single message mode
18
- newo sandbox --actor <id> "message" # continue existing sandbox conversation with chat ID
19
+ newo sandbox --actor <id> "message" # continue existing sandbox conversation
19
20
  newo pull-attributes [--customer <idn>] # download customer + project attributes
20
- newo list-customers # list available customers and their configuration
21
- newo meta [--customer <idn>] # get project metadata (debug command)
22
- newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles from structured text file
21
+ newo list-customers # list available customers
22
+ newo meta [--customer <idn>] # get project metadata (debug)
23
+ newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles
23
24
 
24
25
  Project Management:
25
26
  newo create-project <idn> [--title <title>] [--description <desc>] [--version <version>] [--auto-update] # create empty project on platform
@@ -75,6 +76,7 @@ Account & Customer Management:
75
76
 
76
77
  Flags:
77
78
  --customer <idn> # specify customer (if not set, uses default or interactive selection)
79
+ --format <cli_v1|newo_v2> # project format (auto-detected from filesystem, or set NEWO_FORMAT in .env)
78
80
  --all # include all available data (for conversations: all personas and acts)
79
81
  --force, -f # force overwrite without prompting (for pull command)
80
82
  --verbose, -v # enable detailed logging and progress information
@@ -82,6 +84,7 @@ Flags:
82
84
  --actor <id> # continue existing sandbox chat with actor/chat ID
83
85
  --confirm # confirm destructive operations without prompting
84
86
  --no-publish # skip automatic flow publishing during push operations
87
+ --output, -o <file> # output file path (for export command)
85
88
 
86
89
  Selective Sync Flags (NEW):
87
90
  --only <resources> # sync only specified resources (comma-separated)
@@ -99,6 +102,7 @@ Selective Sync Flags (NEW):
99
102
 
100
103
  Environment Variables:
101
104
  NEWO_BASE_URL # NEWO API base URL (default: https://app.newo.ai)
105
+ NEWO_FORMAT # Default format for new pulls: cli_v1 (default) or newo_v2
102
106
 
103
107
  Single Customer:
104
108
  NEWO_API_KEY # API key for single customer setup
@@ -200,37 +204,45 @@ Usage Examples:
200
204
  newo create-customer "Test Co" --email test@test.com --status temporal # Temporal (trial) customer
201
205
  newo create-customer "Partner" --email p@p.com --project nac_integration --auto-update # With project template
202
206
 
203
- File Structure:
204
- newo_customers/
205
- ā”œā”€ā”€ <customer-idn>/
206
- │ ā”œā”€ā”€ attributes.yaml # Customer attributes (pull-attributes)
207
- │ ā”œā”€ā”€ conversations.yaml # User conversations and personas
208
- │ ā”œā”€ā”€ akb/ # AKB knowledge base articles (pull-akb)
209
- │ │ └── <agent-idn>.yaml # AKB articles per agent persona
210
- │ ā”œā”€ā”€ integrations/ # Integration configurations (pull-integrations)
211
- │ │ ā”œā”€ā”€ integrations.yaml # Master integrations list
212
- │ │ └── <integration-idn>/
213
- │ │ ā”œā”€ā”€ <integration-idn>.yaml # Integration metadata + settings (combined)
214
- │ │ └── connectors/
215
- │ │ └── <connector-idn>/ # Each connector in own directory
216
- │ │ ā”œā”€ā”€ <connector-idn>.yaml # Connector config
217
- │ │ └── webhooks/ # Webhooks subdirectory (if any)
218
- │ │ ā”œā”€ā”€ outgoing.yaml # Outgoing webhooks
219
- │ │ └── incoming.yaml # Incoming webhooks
220
- │ └── projects/
221
- │ └── <project-idn>/
222
- │ ā”œā”€ā”€ attributes.yaml # Project attributes (pull-attributes)
223
- │ ā”œā”€ā”€ flows.yaml # Auto-generated project structure
224
- │ ā”œā”€ā”€ metadata.yaml # Project metadata
225
- │ └── <agent-idn>/
226
- │ ā”œā”€ā”€ metadata.yaml # Agent metadata
227
- │ └── <flow-idn>/
228
- │ ā”œā”€ā”€ metadata.yaml # Flow metadata
229
- │ └── <skill-idn>/
230
- │ ā”œā”€ā”€ skill.guidance # AI guidance scripts
231
- │ ā”œā”€ā”€ skill.jinja # NSL/Jinja template scripts
232
- │ └── metadata.yaml # Skill metadata
233
- └── .newo/ # CLI state and mappings (auto-generated)
207
+ File Structure (cli_v1 - default):
208
+ newo_customers/<customer-idn>/
209
+ ā”œā”€ā”€ attributes.yaml # Customer attributes
210
+ ā”œā”€ā”€ conversations.yaml # User conversations
211
+ ā”œā”€ā”€ akb/<agent-idn>.yaml # AKB knowledge base
212
+ ā”œā”€ā”€ integrations/ # Integration configurations
213
+ └── projects/<project-idn>/
214
+ ā”œā”€ā”€ metadata.yaml # Project metadata
215
+ ā”œā”€ā”€ flows.yaml # Auto-generated structure
216
+ ā”œā”€ā”€ attributes.yaml # Project attributes
217
+ ā”œā”€ā”€ libraries/<lib-idn>/ # Shared skill libraries
218
+ │ ā”œā”€ā”€ metadata.yaml
219
+ │ └── <skill-idn>/<skill>.jinja|.guidance
220
+ └── <agent-idn>/<flow-idn>/<skill-idn>/
221
+ ā”œā”€ā”€ <skill>.guidance|.jinja # Skill scripts
222
+ └── metadata.yaml # Skill metadata
223
+
224
+ File Structure (newo_v2 - platform compatible):
225
+ newo_customers/<customer-idn>/
226
+ ā”œā”€ā”€ import_version.txt # Format marker (v2.0.0)
227
+ ā”œā”€ā”€ attributes.yaml # Customer attributes
228
+ ā”œā”€ā”€ akb/<agent-idn>.yaml # AKB knowledge base
229
+ └── <project-idn>/
230
+ ā”œā”€ā”€ <project-idn>.yaml # Project metadata
231
+ ā”œā”€ā”€ attributes.yaml # Project attributes
232
+ ā”œā”€ā”€ libraries/<lib-idn>/ # Shared skill libraries
233
+ │ ā”œā”€ā”€ <lib-idn>.yaml # Library + inline skill defs
234
+ │ └── skills/<skill>.nsl|.nslg
235
+ └── agents/<agent-idn>/
236
+ ā”œā”€ā”€ agent.yaml # Agent metadata
237
+ └── flows/<flow-idn>/
238
+ ā”œā”€ā”€ <flow-idn>.yaml # Flow + inline skill defs + events
239
+ └── skills/<skill>.nsl|.nslg # Skill scripts
240
+
241
+ Format Detection:
242
+ Format is auto-detected per customer from the filesystem:
243
+ - import_version.txt present -> newo_v2
244
+ - projects/ directory present -> cli_v1
245
+ Set NEWO_FORMAT=newo_v2 in .env for new pulls, or use --format flag
234
246
 
235
247
  For more information, visit: https://github.com/sabbah13/newo-cli
236
248
  `);
@@ -15,6 +15,8 @@ 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
17
  import type { MultiCustomerConfig, CliArgs, CustomerConfig } from '../../types.js';
18
+ import { resolveFormat } from '../../format/detect.js';
19
+ import type { FormatVersion } from '../../format/types.js';
18
20
 
19
21
  /**
20
22
  * Parse resource list from comma-separated string
@@ -44,16 +46,17 @@ function validateResources(resources: string[]): { valid: string[]; invalid: str
44
46
  }
45
47
 
46
48
  /**
47
- * Pull using V2 SyncEngine with selective sync
49
+ * Pull using SyncEngine with selective sync and format support
48
50
  */
49
- async function pullWithV2Engine(
51
+ async function pullWithSyncEngine(
50
52
  customerConfig: MultiCustomerConfig,
51
53
  customer: CustomerConfig,
52
54
  resources: string[] | 'all',
53
55
  verbose: boolean,
54
- silentOverwrite: boolean
56
+ silentOverwrite: boolean,
57
+ formatVersion?: FormatVersion
55
58
  ): Promise<void> {
56
- const { syncEngine, logger } = setupCli(customerConfig, verbose);
59
+ const { syncEngine, logger } = setupCli(customerConfig, verbose, formatVersion);
57
60
 
58
61
  const pullOptions = {
59
62
  verbose,
@@ -126,28 +129,56 @@ export async function handlePullCommand(
126
129
  console.log(`šŸ“¦ Pulling ALL resources`);
127
130
  }
128
131
 
129
- // Use V2 engine if selective sync requested, otherwise use legacy for backward compatibility
130
- const useV2Engine = onlyResources.length > 0 || excludeResources.length > 0 || pullAllResources;
132
+ // Use SyncEngine if selective sync requested, otherwise use legacy for backward compatibility
133
+ const useSyncEngine = onlyResources.length > 0 || excludeResources.length > 0 || pullAllResources;
134
+
135
+ // Explicit --format flag (applies to all customers in this run)
136
+ const explicitFormat = args.format as string | undefined;
131
137
 
132
138
  if (selectedCustomer) {
133
- if (useV2Engine) {
134
- await pullWithV2Engine(customerConfig, selectedCustomer, resourcesToFetch, verbose, silentOverwrite);
139
+ // Resolve format for this customer
140
+ const formatConfig = resolveFormat(selectedCustomer.idn, explicitFormat);
141
+ const isV2Format = formatConfig.version === 'newo_v2';
142
+
143
+ if (verbose || isV2Format) {
144
+ const sourceLabel = formatConfig.source === 'auto-detected' ? 'auto-detected'
145
+ : formatConfig.source === 'env-var' ? 'from .env'
146
+ : formatConfig.source === 'explicit-flag' ? '--format flag'
147
+ : 'default';
148
+ console.log(`Format: ${formatConfig.version} [${sourceLabel}]`);
149
+ }
150
+
151
+ // If format is newo_v2, always use SyncEngine (which will use V2ProjectSyncStrategy)
152
+ if (useSyncEngine || isV2Format) {
153
+ await pullWithSyncEngine(customerConfig, selectedCustomer, resourcesToFetch, verbose, silentOverwrite, formatConfig.version);
135
154
  } else {
136
- // Legacy behavior: pull projects + attributes only
155
+ // Legacy behavior: pull projects + attributes only (cli_v1)
137
156
  const accessToken = await getValidAccessToken(selectedCustomer);
138
157
  const client = await makeClient(verbose, accessToken);
139
158
  const projectId = selectedCustomer.projectId || null;
140
159
  await pullAll(client, selectedCustomer, projectId, verbose, silentOverwrite);
141
160
  }
142
161
  } else if (isMultiCustomer) {
143
- if (verbose) console.log(`šŸ“„ No default customer specified, pulling from all ${allCustomers.length} customers`);
144
- console.log(`šŸ”„ Pulling from ${allCustomers.length} customers...`);
162
+ if (verbose) console.log(`No default customer specified, pulling from all ${allCustomers.length} customers`);
163
+ console.log(`Pulling from ${allCustomers.length} customers...`);
145
164
 
146
165
  for (const customer of allCustomers) {
147
- console.log(`\nšŸ“„ Pulling from customer: ${customer.idn}`);
166
+ console.log(`\nPulling from customer: ${customer.idn}`);
167
+
168
+ // Resolve format per customer (auto-detect from filesystem)
169
+ const formatConfig = resolveFormat(customer.idn, explicitFormat);
170
+ const isV2Format = formatConfig.version === 'newo_v2';
171
+
172
+ if (verbose || isV2Format) {
173
+ const sourceLabel = formatConfig.source === 'auto-detected' ? 'auto-detected'
174
+ : formatConfig.source === 'env-var' ? 'from .env'
175
+ : formatConfig.source === 'explicit-flag' ? '--format flag'
176
+ : 'default';
177
+ console.log(` Format: ${formatConfig.version} [${sourceLabel}]`);
178
+ }
148
179
 
149
- if (useV2Engine) {
150
- await pullWithV2Engine(customerConfig, customer, resourcesToFetch, verbose, silentOverwrite);
180
+ if (useSyncEngine || isV2Format) {
181
+ await pullWithSyncEngine(customerConfig, customer, resourcesToFetch, verbose, silentOverwrite, formatConfig.version);
151
182
  } else {
152
183
  const accessToken = await getValidAccessToken(customer);
153
184
  const client = await makeClient(verbose, accessToken);
@@ -155,6 +186,6 @@ export async function handlePullCommand(
155
186
  await pullAll(client, customer, projectId, verbose, silentOverwrite);
156
187
  }
157
188
  }
158
- console.log(`\nāœ… Pull completed for all ${allCustomers.length} customers`);
189
+ console.log(`\nPull completed for all ${allCustomers.length} customers`);
159
190
  }
160
191
  }
@@ -16,6 +16,8 @@ import { selectSingleCustomer, interactiveCustomerSelection } from '../customer-
16
16
  import { setupCli } from '../../cli-new/bootstrap.js';
17
17
  import { PUSHABLE_RESOURCE_TYPES } from '../../cli-new/di/tokens.js';
18
18
  import type { MultiCustomerConfig, CliArgs, CustomerConfig } from '../../types.js';
19
+ import { resolveFormat } from '../../format/detect.js';
20
+ import type { FormatVersion } from '../../format/types.js';
19
21
 
20
22
  /**
21
23
  * Parse resource list from comma-separated string
@@ -47,15 +49,16 @@ function validateResources(resources: string[]): { valid: string[]; invalid: str
47
49
  }
48
50
 
49
51
  /**
50
- * Push using V2 SyncEngine with selective sync
52
+ * Push using SyncEngine with selective sync and format support
51
53
  */
52
- async function pushWithV2Engine(
54
+ async function pushWithSyncEngine(
53
55
  customerConfig: MultiCustomerConfig,
54
56
  customer: CustomerConfig,
55
57
  resources: string[] | 'all',
56
- verbose: boolean
58
+ verbose: boolean,
59
+ formatVersion?: FormatVersion
57
60
  ): Promise<void> {
58
- const { syncEngine, logger } = setupCli(customerConfig, verbose);
61
+ const { syncEngine, logger } = setupCli(customerConfig, verbose, formatVersion);
59
62
 
60
63
  if (resources === 'all') {
61
64
  const result = await syncEngine.pushAll(customer);
@@ -124,44 +127,48 @@ export async function handlePushCommand(
124
127
  console.log(`šŸ“¦ Pushing ALL resources`);
125
128
  }
126
129
 
127
- // Use V2 engine if selective sync requested, otherwise use legacy for backward compatibility
128
- const useV2Engine = onlyResources.length > 0 || excludeResources.length > 0 || pushAllResources;
130
+ // Use SyncEngine if selective sync requested, otherwise use legacy for backward compatibility
131
+ const useSyncEngine = onlyResources.length > 0 || excludeResources.length > 0 || pushAllResources;
132
+ const explicitFormat = args.format as string | undefined;
133
+
134
+ /**
135
+ * Push for a single customer with format detection
136
+ */
137
+ async function pushForCustomer(customer: CustomerConfig): Promise<void> {
138
+ const formatConfig = resolveFormat(customer.idn, explicitFormat);
139
+ const isV2Format = formatConfig.version === 'newo_v2';
140
+
141
+ if (verbose || isV2Format) {
142
+ const sourceLabel = formatConfig.source === 'auto-detected' ? 'auto-detected'
143
+ : formatConfig.source === 'env-var' ? 'from .env'
144
+ : formatConfig.source === 'explicit-flag' ? '--format flag'
145
+ : 'default';
146
+ console.log(`Format: ${formatConfig.version} [${sourceLabel}]`);
147
+ }
129
148
 
130
- if (selectedCustomer) {
131
- if (useV2Engine) {
132
- await pushWithV2Engine(customerConfig, selectedCustomer, resourcesToPush, verbose);
149
+ if (useSyncEngine || isV2Format) {
150
+ await pushWithSyncEngine(customerConfig, customer, resourcesToPush, verbose, formatConfig.version);
133
151
  } else {
134
- // Legacy behavior
135
- const accessToken = await getValidAccessToken(selectedCustomer);
152
+ const accessToken = await getValidAccessToken(customer);
136
153
  const client = await makeClient(verbose, accessToken);
137
- await pushChanged(client, selectedCustomer, verbose, shouldPublish);
154
+ await pushChanged(client, customer, verbose, shouldPublish);
138
155
  }
156
+ }
157
+
158
+ if (selectedCustomer) {
159
+ await pushForCustomer(selectedCustomer);
139
160
  } else if (isMultiCustomer) {
140
- // Multiple customers exist with no default, ask user
141
161
  const customersToProcess = await interactiveCustomerSelection(allCustomers);
142
162
 
143
163
  if (customersToProcess.length === 1) {
144
- const customer = customersToProcess[0]!;
145
- if (useV2Engine) {
146
- await pushWithV2Engine(customerConfig, customer, resourcesToPush, verbose);
147
- } else {
148
- const accessToken = await getValidAccessToken(customer);
149
- const client = await makeClient(verbose, accessToken);
150
- await pushChanged(client, customer, verbose, shouldPublish);
151
- }
164
+ await pushForCustomer(customersToProcess[0]!);
152
165
  } else {
153
- console.log(`šŸ”„ Pushing to ${customersToProcess.length} customers...`);
166
+ console.log(`Pushing to ${customersToProcess.length} customers...`);
154
167
  for (const customer of customersToProcess) {
155
- console.log(`\nšŸ“¤ Pushing for customer: ${customer.idn}`);
156
- if (useV2Engine) {
157
- await pushWithV2Engine(customerConfig, customer, resourcesToPush, verbose);
158
- } else {
159
- const accessToken = await getValidAccessToken(customer);
160
- const client = await makeClient(verbose, accessToken);
161
- await pushChanged(client, customer, verbose, shouldPublish);
162
- }
168
+ console.log(`\nPushing for customer: ${customer.idn}`);
169
+ await pushForCustomer(customer);
163
170
  }
164
- console.log(`\nāœ… Push completed for all ${customersToProcess.length} customers`);
171
+ console.log(`\nPush completed for all ${customersToProcess.length} customers`);
165
172
  }
166
173
  }
167
174
  }
@@ -1,9 +1,59 @@
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';
6
- import type { MultiCustomerConfig, CliArgs } from '../../types.js';
10
+ import { setupCli } from '../../cli-new/bootstrap.js';
11
+ import { resolveFormat } from '../../format/detect.js';
12
+ import type { MultiCustomerConfig, CliArgs, CustomerConfig } from '../../types.js';
13
+
14
+ async function statusForCustomer(
15
+ customerConfig: MultiCustomerConfig,
16
+ customer: CustomerConfig,
17
+ explicitFormat: string | undefined,
18
+ verbose: boolean
19
+ ): Promise<void> {
20
+ const formatConfig = resolveFormat(customer.idn, explicitFormat);
21
+ const isV2Format = formatConfig.version === 'newo_v2';
22
+
23
+ if (verbose || isV2Format) {
24
+ const sourceLabel = formatConfig.source === 'auto-detected' ? 'auto-detected'
25
+ : formatConfig.source === 'env-var' ? 'from .env'
26
+ : formatConfig.source === 'explicit-flag' ? '--format flag'
27
+ : 'default';
28
+ console.log(`Format: ${formatConfig.version} [${sourceLabel}]`);
29
+ }
30
+
31
+ if (isV2Format) {
32
+ // Use SyncEngine with V2ProjectSyncStrategy
33
+ const { syncEngine, logger } = setupCli(customerConfig, verbose, formatConfig.version);
34
+ const report = await syncEngine.getStatus(customer);
35
+
36
+ logger.info(`\nStatus for customer: ${report.customer}`);
37
+ logger.info(`Total changes: ${report.totalChanges}\n`);
38
+
39
+ for (const resource of report.resources) {
40
+ if (resource.changedCount > 0) {
41
+ logger.info(`${resource.displayName}: ${resource.changedCount} change(s)`);
42
+ for (const change of resource.changes) {
43
+ const op = change.operation.toUpperCase().charAt(0);
44
+ logger.info(` ${op} ${change.path}`);
45
+ }
46
+ }
47
+ }
48
+
49
+ if (report.totalChanges === 0) {
50
+ logger.info('No changes to push.');
51
+ }
52
+ } else {
53
+ // Legacy V1 status
54
+ await status(customer, verbose);
55
+ }
56
+ }
7
57
 
8
58
  export async function handleStatusCommand(
9
59
  customerConfig: MultiCustomerConfig,
@@ -15,16 +65,16 @@ export async function handleStatusCommand(
15
65
  args.customer as string | undefined
16
66
  );
17
67
 
68
+ const explicitFormat = args.format as string | undefined;
69
+
18
70
  if (selectedCustomer) {
19
- // Single customer status
20
- await status(selectedCustomer, verbose);
71
+ await statusForCustomer(customerConfig, selectedCustomer, explicitFormat, verbose);
21
72
  } else if (isMultiCustomer) {
22
- // Multi-customer status
23
- console.log(`šŸ”„ Checking status for ${allCustomers.length} customers...`);
73
+ console.log(`Checking status for ${allCustomers.length} customers...`);
24
74
  for (const customer of allCustomers) {
25
- console.log(`\nšŸ“‹ Status for customer: ${customer.idn}`);
26
- await status(customer, verbose);
75
+ console.log(`\nStatus for customer: ${customer.idn}`);
76
+ await statusForCustomer(customerConfig, customer, explicitFormat, verbose);
27
77
  }
28
- console.log(`\nāœ… Status check completed for all ${allCustomers.length} customers`);
78
+ console.log(`\nStatus check completed for all ${allCustomers.length} customers`);
29
79
  }
30
- }
80
+ }
@@ -11,6 +11,8 @@ import { ConsoleLogger, type ILogger, type CustomerConfig, type MultiCustomerCon
11
11
  import { SyncEngine, type SyncEngineOptions } from '../application/sync/SyncEngine.js';
12
12
  import { MigrationEngine, TransformService } from '../application/migration/MigrationEngine.js';
13
13
  import { ProjectSyncStrategy, createProjectSyncStrategy } from '../domain/strategies/sync/ProjectSyncStrategy.js';
14
+ import { createV2ProjectSyncStrategy } from '../domain/strategies/sync/V2ProjectSyncStrategy.js';
15
+ import type { FormatVersion } from '../format/types.js';
14
16
  import { AttributeSyncStrategy, createAttributeSyncStrategy } from '../domain/strategies/sync/AttributeSyncStrategy.js';
15
17
  import { IntegrationSyncStrategy, createIntegrationSyncStrategy } from '../domain/strategies/sync/IntegrationSyncStrategy.js';
16
18
  import { AkbSyncStrategy, createAkbSyncStrategy } from '../domain/strategies/sync/AkbSyncStrategy.js';
@@ -30,7 +32,7 @@ export async function createApiClient(customer: CustomerConfig, verbose: boolean
30
32
  process.env.NEWO_PROJECT_ID = customer.projectId;
31
33
  }
32
34
 
33
- const token = await getValidAccessToken();
35
+ const token = await getValidAccessToken(customer);
34
36
  return makeClient(verbose, token);
35
37
  }
36
38
 
@@ -47,6 +49,12 @@ export interface BootstrapOptions {
47
49
  * Sync engine options
48
50
  */
49
51
  syncEngineOptions?: SyncEngineOptions;
52
+
53
+ /**
54
+ * Format version for project sync strategy
55
+ * 'newo_v2' uses V2ProjectSyncStrategy, 'cli_v1' (default) uses ProjectSyncStrategy
56
+ */
57
+ formatVersion?: FormatVersion | undefined;
50
58
  }
51
59
 
52
60
  /**
@@ -73,10 +81,13 @@ export function createServiceContainer(
73
81
 
74
82
  // === Domain Layer - Sync Strategies ===
75
83
 
76
- // Project Sync Strategy
77
- container.registerSingleton(TOKENS.PROJECT_SYNC_STRATEGY, () =>
78
- createProjectSyncStrategy(createApiClient, container.get<ILogger>(TOKENS.LOGGER))
79
- );
84
+ // Project Sync Strategy (format-conditional: cli_v1 or newo_v2)
85
+ container.registerSingleton(TOKENS.PROJECT_SYNC_STRATEGY, () => {
86
+ if (options.formatVersion === 'newo_v2') {
87
+ return createV2ProjectSyncStrategy(createApiClient, container.get<ILogger>(TOKENS.LOGGER));
88
+ }
89
+ return createProjectSyncStrategy(createApiClient, container.get<ILogger>(TOKENS.LOGGER));
90
+ });
80
91
 
81
92
  // Attribute Sync Strategy
82
93
  container.registerSingleton(TOKENS.ATTRIBUTE_SYNC_STRATEGY, () =>
@@ -151,14 +162,15 @@ export function getLogger(container: ServiceContainer): ILogger {
151
162
  */
152
163
  export function setupCli(
153
164
  customerConfig: MultiCustomerConfig,
154
- verbose: boolean = false
165
+ verbose: boolean = false,
166
+ formatVersion?: FormatVersion
155
167
  ): {
156
168
  container: ServiceContainer;
157
169
  syncEngine: SyncEngine;
158
170
  migrationEngine: MigrationEngine;
159
171
  logger: ILogger;
160
172
  } {
161
- const container = createServiceContainer(customerConfig, { verbose });
173
+ const container = createServiceContainer(customerConfig, { verbose, formatVersion });
162
174
 
163
175
  return {
164
176
  container,
@@ -64,6 +64,7 @@ export const TOKENS = {
64
64
  // Configuration
65
65
  CUSTOMER_CONFIG: Symbol('CustomerConfig'),
66
66
  ENVIRONMENT: Symbol('Environment'),
67
+ FORMAT_VERSION: Symbol('FormatVersion'),
67
68
  } as const;
68
69
 
69
70
  /**
package/src/cli.ts CHANGED
@@ -46,6 +46,7 @@ import { handleAddProjectCommand } from './cli/commands/add-project.js';
46
46
  import { handleWatchCommand } from './cli/commands/watch.js';
47
47
  import { handleDiffCommand } from './cli/commands/diff.js';
48
48
  import { handleLogsCommand } from './cli/commands/logs.js';
49
+ import { handleExportCommand } from './cli/commands/export.js';
49
50
  import type { CliArgs, NewoApiError } from './types.js';
50
51
 
51
52
  dotenv.config();
@@ -111,6 +112,10 @@ async function main(): Promise<void> {
111
112
  await handleLogsCommand(customerConfig, args, verbose);
112
113
  break;
113
114
 
115
+ case 'export':
116
+ await handleExportCommand(customerConfig, args, verbose);
117
+ break;
118
+
114
119
  case 'conversations':
115
120
  await handleConversationsCommand(customerConfig, args, verbose);
116
121
  break;