newo 3.4.1 ā 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.
- package/.env.example +5 -0
- package/CHANGELOG.md +31 -0
- package/dist/api.d.ts +18 -0
- package/dist/api.js +28 -0
- package/dist/cli/commands/create-attribute.js +1 -1
- package/dist/cli/commands/export.d.ts +3 -0
- package/dist/cli/commands/export.js +62 -0
- package/dist/cli/commands/help.js +54 -42
- package/dist/cli/commands/pull.js +38 -14
- package/dist/cli/commands/push.js +32 -32
- package/dist/cli/commands/status.js +46 -7
- package/dist/cli/commands/update-attribute.d.ts +3 -0
- package/dist/cli/commands/update-attribute.js +78 -0
- package/dist/cli-new/bootstrap.d.ts +7 -1
- package/dist/cli-new/bootstrap.js +11 -5
- package/dist/cli-new/di/tokens.d.ts +1 -0
- package/dist/cli-new/di/tokens.js +1 -0
- package/dist/cli.js +8 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +5 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.js +97 -8
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.d.ts +80 -0
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.js +725 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.js +1 -0
- package/dist/format/detect.d.ts +14 -0
- package/dist/format/detect.js +105 -0
- package/dist/format/extensions.d.ts +26 -0
- package/dist/format/extensions.js +45 -0
- package/dist/format/index.d.ts +11 -0
- package/dist/format/index.js +11 -0
- package/dist/format/paths-v2.d.ts +31 -0
- package/dist/format/paths-v2.js +104 -0
- package/dist/format/types.d.ts +28 -0
- package/dist/format/types.js +21 -0
- package/dist/format/v2-yaml.d.ts +143 -0
- package/dist/format/v2-yaml.js +222 -0
- package/dist/format/yaml-patch.d.ts +14 -0
- package/dist/format/yaml-patch.js +184 -0
- package/dist/fsutil.d.ts +10 -0
- package/dist/fsutil.js +25 -0
- package/dist/sync/attributes.js +3 -3
- package/dist/sync/skill-files.js +2 -2
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
- package/src/api.ts +64 -0
- package/src/cli/commands/create-attribute.ts +1 -1
- package/src/cli/commands/export.ts +78 -0
- package/src/cli/commands/help.ts +54 -42
- package/src/cli/commands/pull.ts +46 -15
- package/src/cli/commands/push.ts +38 -31
- package/src/cli/commands/status.ts +59 -9
- package/src/cli/commands/update-attribute.ts +82 -0
- package/src/cli-new/bootstrap.ts +19 -7
- package/src/cli-new/di/tokens.ts +1 -0
- package/src/cli.ts +10 -0
- package/src/domain/strategies/sync/ProjectSyncStrategy.ts +122 -8
- package/src/domain/strategies/sync/V2ProjectSyncStrategy.ts +1007 -0
- package/src/env.ts +2 -0
- package/src/format/detect.ts +123 -0
- package/src/format/extensions.ts +61 -0
- package/src/format/index.ts +66 -0
- package/src/format/paths-v2.ts +207 -0
- package/src/format/types.ts +40 -0
- package/src/format/v2-yaml.ts +345 -0
- package/src/format/yaml-patch.ts +208 -0
- package/src/fsutil.ts +37 -0
- package/src/sync/attributes.ts +3 -3
- package/src/sync/skill-files.ts +2 -2
- package/src/types.ts +6 -0
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
16
|
+
|
|
17
|
+
export async function handleExportCommand(
|
|
18
|
+
customerConfig: MultiCustomerConfig,
|
|
19
|
+
args: CliArgs,
|
|
20
|
+
verbose: boolean
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const { selectedCustomer } = selectSingleCustomer(
|
|
23
|
+
customerConfig,
|
|
24
|
+
args.customer as string | undefined
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (!selectedCustomer) {
|
|
28
|
+
console.error('Please specify a customer with --customer');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
33
|
+
const client = await makeClient(verbose, accessToken);
|
|
34
|
+
|
|
35
|
+
// Get customer ID from token (needed for V2 export API)
|
|
36
|
+
const customerId = await getCustomerIdFromToken(accessToken);
|
|
37
|
+
if (!customerId) {
|
|
38
|
+
console.error('Could not determine customer ID from token');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const exportAkb = !args['no-akb'];
|
|
43
|
+
const exportAttributes = !args['no-attributes'];
|
|
44
|
+
|
|
45
|
+
console.log(`Downloading V2 export for customer ${selectedCustomer.idn}...`);
|
|
46
|
+
|
|
47
|
+
const zipBuffer = await exportCustomerV2(client, customerId, {
|
|
48
|
+
export_akb: exportAkb,
|
|
49
|
+
export_customer_attributes: exportAttributes,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Determine output path
|
|
53
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
54
|
+
const defaultName = `export-${selectedCustomer.idn}-${timestamp}.zip`;
|
|
55
|
+
const outputPath = (args.output as string | undefined) || (args.o as string | undefined)
|
|
56
|
+
|| path.join(process.cwd(), 'temp', defaultName);
|
|
57
|
+
|
|
58
|
+
await fs.ensureDir(path.dirname(outputPath));
|
|
59
|
+
await fs.writeFile(outputPath, zipBuffer);
|
|
60
|
+
|
|
61
|
+
console.log(`Exported ${(zipBuffer.length / 1024 / 1024).toFixed(1)}MB to ${outputPath}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract customer_id from JWT token payload
|
|
66
|
+
*/
|
|
67
|
+
function getCustomerIdFromToken(token: string): string | null {
|
|
68
|
+
try {
|
|
69
|
+
const parts = token.split('.');
|
|
70
|
+
if (parts.length < 2) return null;
|
|
71
|
+
const payload = parts[1]!;
|
|
72
|
+
const padded = payload + '='.repeat(4 - (payload.length % 4));
|
|
73
|
+
const decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8')) as Record<string, unknown>;
|
|
74
|
+
return (decoded['customer_id'] as string) || (decoded['sub'] as string) || null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
package/src/cli/commands/help.ts
CHANGED
|
@@ -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>]
|
|
11
|
-
newo push [--customer <idn>] [--
|
|
12
|
-
newo status [--customer <idn>]
|
|
13
|
-
newo
|
|
14
|
-
newo
|
|
15
|
-
newo
|
|
16
|
-
newo
|
|
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
|
|
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
|
|
21
|
-
newo meta [--customer <idn>] # get project metadata (debug
|
|
22
|
-
newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles
|
|
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
|
-
āāā
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
`);
|
package/src/cli/commands/pull.ts
CHANGED
|
@@ -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
|
|
49
|
+
* Pull using SyncEngine with selective sync and format support
|
|
48
50
|
*/
|
|
49
|
-
async function
|
|
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
|
|
130
|
-
const
|
|
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
|
-
|
|
134
|
-
|
|
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(
|
|
144
|
-
console.log(
|
|
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(`\
|
|
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 (
|
|
150
|
-
await
|
|
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(`\
|
|
189
|
+
console.log(`\nPull completed for all ${allCustomers.length} customers`);
|
|
159
190
|
}
|
|
160
191
|
}
|
package/src/cli/commands/push.ts
CHANGED
|
@@ -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
|
|
52
|
+
* Push using SyncEngine with selective sync and format support
|
|
51
53
|
*/
|
|
52
|
-
async function
|
|
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
|
|
128
|
-
const
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
await pushWithV2Engine(customerConfig, selectedCustomer, resourcesToPush, verbose);
|
|
149
|
+
if (useSyncEngine || isV2Format) {
|
|
150
|
+
await pushWithSyncEngine(customerConfig, customer, resourcesToPush, verbose, formatConfig.version);
|
|
133
151
|
} else {
|
|
134
|
-
|
|
135
|
-
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
152
|
+
const accessToken = await getValidAccessToken(customer);
|
|
136
153
|
const client = await makeClient(verbose, accessToken);
|
|
137
|
-
await pushChanged(client,
|
|
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
|
-
|
|
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(
|
|
166
|
+
console.log(`Pushing to ${customersToProcess.length} customers...`);
|
|
154
167
|
for (const customer of customersToProcess) {
|
|
155
|
-
console.log(`\
|
|
156
|
-
|
|
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(`\
|
|
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
|
|
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
|
-
|
|
20
|
-
await status(selectedCustomer, verbose);
|
|
71
|
+
await statusForCustomer(customerConfig, selectedCustomer, explicitFormat, verbose);
|
|
21
72
|
} else if (isMultiCustomer) {
|
|
22
|
-
|
|
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(`\
|
|
26
|
-
await
|
|
75
|
+
console.log(`\nStatus for customer: ${customer.idn}`);
|
|
76
|
+
await statusForCustomer(customerConfig, customer, explicitFormat, verbose);
|
|
27
77
|
}
|
|
28
|
-
console.log(`\
|
|
78
|
+
console.log(`\nStatus check completed for all ${allCustomers.length} customers`);
|
|
29
79
|
}
|
|
30
|
-
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Customer Attribute Command Handler
|
|
3
|
+
*/
|
|
4
|
+
import { makeClient, getCustomerAttributes, updateCustomerAttribute } from '../../api.js';
|
|
5
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
6
|
+
import { requireSingleCustomer } from '../customer-selection.js';
|
|
7
|
+
import type { MultiCustomerConfig, CliArgs, CustomerAttribute } from '../../types.js';
|
|
8
|
+
|
|
9
|
+
export async function handleUpdateAttributeCommand(
|
|
10
|
+
customerConfig: MultiCustomerConfig,
|
|
11
|
+
args: CliArgs,
|
|
12
|
+
verbose: boolean = false
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
try {
|
|
15
|
+
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer as string | undefined);
|
|
16
|
+
|
|
17
|
+
// Parse arguments
|
|
18
|
+
const idn = args._[1] as string;
|
|
19
|
+
|
|
20
|
+
if (!idn) {
|
|
21
|
+
console.error('Error: Attribute IDN is required');
|
|
22
|
+
console.error('Usage: newo update-attribute <idn> [--value <value>] [--title <title>] [--description <desc>] [--group <group>] [--hidden] [--value-type <type>] [--possible-values <val1,val2>]');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Get access token and create client
|
|
27
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
28
|
+
const client = await makeClient(verbose, accessToken);
|
|
29
|
+
|
|
30
|
+
// Fetch existing attributes to find the one to update
|
|
31
|
+
const response = await getCustomerAttributes(client, true);
|
|
32
|
+
const existing = response.attributes.find((a: CustomerAttribute) => a.idn === idn);
|
|
33
|
+
|
|
34
|
+
if (!existing) {
|
|
35
|
+
console.error(`ā Attribute '${idn}' not found. Use 'newo create-attribute' to create it.`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!existing.id) {
|
|
40
|
+
console.error(`ā Attribute '${idn}' has no ID. Cannot update.`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Build updated attribute - only override fields that were explicitly provided
|
|
45
|
+
const updated: CustomerAttribute = {
|
|
46
|
+
id: existing.id,
|
|
47
|
+
idn: existing.idn,
|
|
48
|
+
value: args.value !== undefined ? String(args.value) : existing.value,
|
|
49
|
+
title: (args.title as string) || existing.title,
|
|
50
|
+
description: (args.description as string) || existing.description,
|
|
51
|
+
group: (args.group as string) || existing.group,
|
|
52
|
+
is_hidden: args.hidden !== undefined ? Boolean(args.hidden) : existing.is_hidden,
|
|
53
|
+
possible_values: args['possible-values']
|
|
54
|
+
? (args['possible-values'] as string).split(',').map(v => v.trim())
|
|
55
|
+
: existing.possible_values,
|
|
56
|
+
value_type: (args['value-type'] as string) || existing.value_type
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (verbose) {
|
|
60
|
+
console.log(`š Updating customer attribute: ${idn} (ID: ${existing.id})`);
|
|
61
|
+
if (args.value !== undefined) console.log(` Value: ${existing.value} -> ${updated.value}`);
|
|
62
|
+
if (args.title) console.log(` Title: ${existing.title} -> ${updated.title}`);
|
|
63
|
+
if (args.description) console.log(` Description: updated`);
|
|
64
|
+
if (args.group) console.log(` Group: ${existing.group} -> ${updated.group}`);
|
|
65
|
+
if (args['value-type']) console.log(` Type: ${existing.value_type} -> ${updated.value_type}`);
|
|
66
|
+
if (args.hidden !== undefined) console.log(` Hidden: ${existing.is_hidden} -> ${updated.is_hidden}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await updateCustomerAttribute(client, updated);
|
|
70
|
+
|
|
71
|
+
console.log(`ā
Customer attribute updated: ${idn}`);
|
|
72
|
+
if (args.value !== undefined) console.log(` Value: ${updated.value}`);
|
|
73
|
+
if (args['value-type']) console.log(` Type: ${updated.value_type}`);
|
|
74
|
+
if (args.title) console.log(` Title: ${updated.title}`);
|
|
75
|
+
if (args.description) console.log(` Description: updated`);
|
|
76
|
+
if (args.group) console.log(` Group: ${updated.group}`);
|
|
77
|
+
|
|
78
|
+
} catch (error: unknown) {
|
|
79
|
+
console.error('ā Failed to update customer attribute:', error instanceof Error ? error.message : String(error));
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|