newo 3.4.0 → 3.4.2

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 (79) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/api.d.ts +3 -1
  3. package/dist/api.js +49 -1
  4. package/dist/application/migration/MigrationEngine.d.ts +141 -0
  5. package/dist/application/migration/MigrationEngine.js +322 -0
  6. package/dist/application/migration/index.d.ts +5 -0
  7. package/dist/application/migration/index.js +5 -0
  8. package/dist/application/sync/SyncEngine.d.ts +134 -0
  9. package/dist/application/sync/SyncEngine.js +335 -0
  10. package/dist/application/sync/index.d.ts +5 -0
  11. package/dist/application/sync/index.js +5 -0
  12. package/dist/cli/commands/create-attribute.js +1 -1
  13. package/dist/cli/commands/create-customer.d.ts +3 -0
  14. package/dist/cli/commands/create-customer.js +159 -0
  15. package/dist/cli/commands/diff.d.ts +6 -0
  16. package/dist/cli/commands/diff.js +288 -0
  17. package/dist/cli/commands/help.js +63 -3
  18. package/dist/cli/commands/logs.d.ts +18 -0
  19. package/dist/cli/commands/logs.js +283 -0
  20. package/dist/cli/commands/pull.js +114 -10
  21. package/dist/cli/commands/push.js +122 -12
  22. package/dist/cli/commands/update-attribute.d.ts +3 -0
  23. package/dist/cli/commands/update-attribute.js +78 -0
  24. package/dist/cli/commands/watch.d.ts +6 -0
  25. package/dist/cli/commands/watch.js +195 -0
  26. package/dist/cli-new/bootstrap.d.ts +74 -0
  27. package/dist/cli-new/bootstrap.js +154 -0
  28. package/dist/cli-new/di/Container.d.ts +64 -0
  29. package/dist/cli-new/di/Container.js +122 -0
  30. package/dist/cli-new/di/tokens.d.ts +77 -0
  31. package/dist/cli-new/di/tokens.js +76 -0
  32. package/dist/cli.js +20 -0
  33. package/dist/domain/resources/common/types.d.ts +71 -0
  34. package/dist/domain/resources/common/types.js +42 -0
  35. package/dist/domain/strategies/sync/AkbSyncStrategy.d.ts +63 -0
  36. package/dist/domain/strategies/sync/AkbSyncStrategy.js +274 -0
  37. package/dist/domain/strategies/sync/AttributeSyncStrategy.d.ts +87 -0
  38. package/dist/domain/strategies/sync/AttributeSyncStrategy.js +378 -0
  39. package/dist/domain/strategies/sync/ConversationSyncStrategy.d.ts +61 -0
  40. package/dist/domain/strategies/sync/ConversationSyncStrategy.js +232 -0
  41. package/dist/domain/strategies/sync/ISyncStrategy.d.ts +149 -0
  42. package/dist/domain/strategies/sync/ISyncStrategy.js +24 -0
  43. package/dist/domain/strategies/sync/IntegrationSyncStrategy.d.ts +68 -0
  44. package/dist/domain/strategies/sync/IntegrationSyncStrategy.js +413 -0
  45. package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +111 -0
  46. package/dist/domain/strategies/sync/ProjectSyncStrategy.js +523 -0
  47. package/dist/domain/strategies/sync/index.d.ts +13 -0
  48. package/dist/domain/strategies/sync/index.js +19 -0
  49. package/dist/sync/migrate.js +99 -23
  50. package/dist/types.d.ts +124 -0
  51. package/package.json +3 -1
  52. package/src/api.ts +53 -2
  53. package/src/application/migration/MigrationEngine.ts +492 -0
  54. package/src/application/migration/index.ts +5 -0
  55. package/src/application/sync/SyncEngine.ts +467 -0
  56. package/src/application/sync/index.ts +5 -0
  57. package/src/cli/commands/create-attribute.ts +1 -1
  58. package/src/cli/commands/create-customer.ts +185 -0
  59. package/src/cli/commands/diff.ts +360 -0
  60. package/src/cli/commands/help.ts +63 -3
  61. package/src/cli/commands/logs.ts +329 -0
  62. package/src/cli/commands/pull.ts +128 -11
  63. package/src/cli/commands/push.ts +131 -13
  64. package/src/cli/commands/update-attribute.ts +82 -0
  65. package/src/cli/commands/watch.ts +227 -0
  66. package/src/cli-new/bootstrap.ts +252 -0
  67. package/src/cli-new/di/Container.ts +152 -0
  68. package/src/cli-new/di/tokens.ts +105 -0
  69. package/src/cli.ts +25 -0
  70. package/src/domain/resources/common/types.ts +106 -0
  71. package/src/domain/strategies/sync/AkbSyncStrategy.ts +358 -0
  72. package/src/domain/strategies/sync/AttributeSyncStrategy.ts +508 -0
  73. package/src/domain/strategies/sync/ConversationSyncStrategy.ts +299 -0
  74. package/src/domain/strategies/sync/ISyncStrategy.ts +182 -0
  75. package/src/domain/strategies/sync/IntegrationSyncStrategy.ts +522 -0
  76. package/src/domain/strategies/sync/ProjectSyncStrategy.ts +747 -0
  77. package/src/domain/strategies/sync/index.ts +46 -0
  78. package/src/sync/migrate.ts +103 -24
  79. package/src/types.ts +135 -0
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Logs command - Fetch and display analytics logs from NEWO platform
3
+ *
4
+ * Usage:
5
+ * newo logs # Last 1 hour of logs
6
+ * newo logs --hours 24 # Last 24 hours
7
+ * newo logs --from "2026-01-11T00:00:00Z" --to "2026-01-12T00:00:00Z"
8
+ * newo logs --level warning # Only warnings
9
+ * newo logs --type call # Only skill calls
10
+ * newo logs --flow CACreatorFlow # Filter by flow
11
+ * newo logs --skill CreateActor # Filter by skill
12
+ * newo logs --follow # Tail mode (poll for new logs)
13
+ * newo logs --json # Output as JSON
14
+ */
15
+ import { makeClient, getLogs } from '../../api.js';
16
+ import { getValidAccessToken } from '../../auth.js';
17
+ import { requireSingleCustomer } from '../customer-selection.js';
18
+ // ANSI color codes for terminal output
19
+ const colors = {
20
+ reset: '\x1b[0m',
21
+ dim: '\x1b[2m',
22
+ red: '\x1b[31m',
23
+ yellow: '\x1b[33m',
24
+ blue: '\x1b[34m',
25
+ cyan: '\x1b[36m',
26
+ green: '\x1b[32m',
27
+ magenta: '\x1b[35m',
28
+ white: '\x1b[37m',
29
+ gray: '\x1b[90m'
30
+ };
31
+ function getLevelColor(level) {
32
+ switch (level) {
33
+ case 'error': return colors.red;
34
+ case 'warning': return colors.yellow;
35
+ case 'info': return colors.blue;
36
+ default: return colors.white;
37
+ }
38
+ }
39
+ function getTypeColor(type) {
40
+ switch (type) {
41
+ case 'call': return colors.cyan;
42
+ case 'operation': return colors.magenta;
43
+ case 'system': return colors.green;
44
+ default: return colors.white;
45
+ }
46
+ }
47
+ function formatLogEntry(log, showColors = true) {
48
+ const c = showColors ? colors : { reset: '', dim: '', red: '', yellow: '', blue: '', cyan: '', green: '', magenta: '', white: '', gray: '' };
49
+ // Format datetime
50
+ const date = new Date(log.datetime);
51
+ const timeStr = date.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
52
+ const msStr = String(date.getMilliseconds()).padStart(3, '0');
53
+ const dateTimeFormatted = `${c.dim}${timeStr}.${msStr}${c.reset}`;
54
+ // Format level with color
55
+ const levelColor = showColors ? getLevelColor(log.level) : '';
56
+ const levelStr = `${levelColor}${log.level.toUpperCase().padEnd(7)}${c.reset}`;
57
+ // Format type with color
58
+ const typeColor = showColors ? getTypeColor(log.log_type) : '';
59
+ const typeStr = `${typeColor}${log.log_type.padEnd(9)}${c.reset}`;
60
+ // Build context string from data
61
+ const contextParts = [];
62
+ if (log.data.flow_idn)
63
+ contextParts.push(`${c.cyan}[${log.data.flow_idn}]${c.reset}`);
64
+ if (log.data.skill_idn)
65
+ contextParts.push(`${c.green}[${log.data.skill_idn}]${c.reset}`);
66
+ if (log.data.line !== undefined)
67
+ contextParts.push(`${c.dim}:${log.data.line}${c.reset}`);
68
+ if (log.data.integration_idn && log.data.connector_idn) {
69
+ contextParts.push(`${c.magenta}${log.data.integration_idn}/${log.data.connector_idn}${c.reset}`);
70
+ }
71
+ const contextStr = contextParts.length > 0 ? ` ${contextParts.join(' ')}` : '';
72
+ // Format message
73
+ const messageStr = log.message;
74
+ return `${dateTimeFormatted} ${levelStr} ${typeStr}${contextStr} ${messageStr}`;
75
+ }
76
+ function formatLogEntryCompact(log, showColors = true) {
77
+ const c = showColors ? colors : { reset: '', dim: '', red: '', yellow: '', blue: '', cyan: '', green: '', magenta: '', white: '', gray: '' };
78
+ // Shorter format for tail mode
79
+ const date = new Date(log.datetime);
80
+ const timeStr = date.toLocaleTimeString('en-US', { hour12: false });
81
+ const levelColor = showColors ? getLevelColor(log.level) : '';
82
+ const levelIcon = log.level === 'error' ? 'āœ—' : log.level === 'warning' ? '⚠' : '•';
83
+ return `${c.dim}${timeStr}${c.reset} ${levelColor}${levelIcon}${c.reset} ${log.message}`;
84
+ }
85
+ export async function handleLogsCommand(customerConfig, args, verbose) {
86
+ // Select customer
87
+ const selectedCustomer = requireSingleCustomer(customerConfig, args.customer);
88
+ console.log(`šŸ“Š Fetching logs for ${selectedCustomer.idn}...`);
89
+ // Get access token and create client
90
+ const token = await getValidAccessToken(selectedCustomer);
91
+ const client = await makeClient(verbose, token);
92
+ // Build query params
93
+ const params = {
94
+ page: args.page ? parseInt(String(args.page), 10) : 1,
95
+ per: args.per ? parseInt(String(args.per), 10) : 50
96
+ };
97
+ // Time range
98
+ if (args.from) {
99
+ params.from_datetime = String(args.from);
100
+ }
101
+ else {
102
+ // Default to last N hours (default 1 hour)
103
+ const hoursAgo = args.hours ? parseInt(String(args.hours), 10) : 1;
104
+ params.from_datetime = new Date(Date.now() - hoursAgo * 60 * 60 * 1000).toISOString();
105
+ }
106
+ if (args.to) {
107
+ params.to_datetime = String(args.to);
108
+ }
109
+ // Filters
110
+ if (args.level) {
111
+ const levelStr = String(args.level);
112
+ const levels = levelStr.split(',');
113
+ if (levels.length === 1 && levels[0]) {
114
+ params.levels = levels[0];
115
+ }
116
+ else if (levels.length > 1) {
117
+ params.levels = levels;
118
+ }
119
+ }
120
+ if (args.type) {
121
+ const typeStr = String(args.type);
122
+ const types = typeStr.split(',');
123
+ if (types.length === 1 && types[0]) {
124
+ params.log_types = types[0];
125
+ }
126
+ else if (types.length > 1) {
127
+ params.log_types = types;
128
+ }
129
+ }
130
+ if (args.project)
131
+ params.project_idn = String(args.project);
132
+ if (args.flow)
133
+ params.flow_idn = String(args.flow);
134
+ if (args.skill)
135
+ params.skill_idn = String(args.skill);
136
+ if (args.message)
137
+ params.message = String(args.message);
138
+ if (args['event-id'])
139
+ params.external_event_id = String(args['event-id']);
140
+ if (args['runtime-id'])
141
+ params.runtime_context_id = String(args['runtime-id']);
142
+ if (args['actor-id'])
143
+ params.user_actor_ids = String(args['actor-id']);
144
+ if (args['persona-id'])
145
+ params.user_persona_ids = String(args['persona-id']);
146
+ const follow = Boolean(args.follow || args.f);
147
+ const asJson = Boolean(args.json);
148
+ const raw = Boolean(args.raw);
149
+ if (follow) {
150
+ await tailLogs(client, params, asJson);
151
+ }
152
+ else {
153
+ await fetchAndDisplayLogs(client, params, asJson, raw);
154
+ }
155
+ }
156
+ async function fetchAndDisplayLogs(client, params, asJson, raw) {
157
+ try {
158
+ const response = await getLogs(client, params);
159
+ const logs = response.items;
160
+ if (asJson) {
161
+ console.log(JSON.stringify(logs, null, 2));
162
+ return;
163
+ }
164
+ if (logs.length === 0) {
165
+ console.log('\nNo logs found for the specified criteria.');
166
+ return;
167
+ }
168
+ console.log(`\nšŸ“ Found ${logs.length} log entries:\n`);
169
+ // Sort by datetime ascending (oldest first)
170
+ const sortedLogs = [...logs].sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime());
171
+ // Detect if stdout is a TTY (supports colors)
172
+ const useColors = process.stdout.isTTY !== false;
173
+ for (const log of sortedLogs) {
174
+ if (raw) {
175
+ console.log(JSON.stringify(log));
176
+ }
177
+ else {
178
+ console.log(formatLogEntry(log, useColors));
179
+ }
180
+ }
181
+ console.log(`\nāœ… Displayed ${logs.length} log entries`);
182
+ }
183
+ catch (error) {
184
+ const err = error;
185
+ console.error('Failed to fetch logs:', err.response?.status, err.response?.data || err.message);
186
+ }
187
+ }
188
+ async function tailLogs(client, params, asJson) {
189
+ console.log('šŸ”„ Watching for new logs (Ctrl+C to stop)...\n');
190
+ const seenLogIds = new Set();
191
+ let lastCheckTime = params.from_datetime || new Date(Date.now() - 60 * 60 * 1000).toISOString();
192
+ const useColors = process.stdout.isTTY !== false;
193
+ // Poll interval (2 seconds)
194
+ const pollInterval = 2000;
195
+ const poll = async () => {
196
+ try {
197
+ const pollParams = {
198
+ ...params,
199
+ from_datetime: lastCheckTime,
200
+ page: 1,
201
+ per: 100
202
+ };
203
+ const response = await getLogs(client, pollParams);
204
+ const logs = response.items;
205
+ // Filter out already seen logs and sort by time
206
+ const newLogs = logs
207
+ .filter(log => !seenLogIds.has(log.log_id))
208
+ .sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime());
209
+ for (const log of newLogs) {
210
+ seenLogIds.add(log.log_id);
211
+ if (asJson) {
212
+ console.log(JSON.stringify(log));
213
+ }
214
+ else {
215
+ console.log(formatLogEntryCompact(log, useColors));
216
+ }
217
+ // Update last check time to the newest log we've seen
218
+ const logTime = new Date(log.datetime);
219
+ const lastTime = new Date(lastCheckTime);
220
+ if (logTime > lastTime) {
221
+ lastCheckTime = log.datetime;
222
+ }
223
+ }
224
+ }
225
+ catch (error) {
226
+ // Silently ignore poll errors to avoid spamming the console
227
+ }
228
+ };
229
+ // Initial poll
230
+ await poll();
231
+ // Set up interval for continuous polling
232
+ const intervalId = setInterval(poll, pollInterval);
233
+ // Handle Ctrl+C gracefully
234
+ process.on('SIGINT', () => {
235
+ clearInterval(intervalId);
236
+ console.log('\n\nšŸ‘‹ Stopped watching logs');
237
+ process.exit(0);
238
+ });
239
+ // Keep the process running
240
+ await new Promise(() => { });
241
+ }
242
+ export function printLogsHelp() {
243
+ console.log(`
244
+ Usage: newo logs [options]
245
+
246
+ Fetch and display analytics logs from the NEWO platform.
247
+
248
+ Time Range Options:
249
+ --hours <n> Show logs from last N hours (default: 1)
250
+ --from <datetime> Start datetime (ISO format, e.g., 2026-01-11T00:00:00Z)
251
+ --to <datetime> End datetime (ISO format)
252
+
253
+ Filter Options:
254
+ --level <levels> Filter by log level: info, warning, error (comma-separated)
255
+ --type <types> Filter by log type: system, operation, call (comma-separated)
256
+ --project <idn> Filter by project IDN
257
+ --flow <idn> Filter by flow IDN
258
+ --skill <idn> Filter by skill IDN
259
+ --message <text> Search in log messages
260
+ --event-id <uuid> Filter by external event ID
261
+ --runtime-id <uuid> Filter by runtime context ID
262
+ --actor-id <uuid> Filter by user actor ID
263
+ --persona-id <uuid> Filter by user persona ID
264
+
265
+ Output Options:
266
+ --json Output logs as JSON
267
+ --raw Output each log as a single JSON line
268
+ --per <n> Number of logs per page (default: 50)
269
+ --page <n> Page number (default: 1)
270
+
271
+ Live Tailing:
272
+ --follow, -f Continuously poll for new logs (like tail -f)
273
+
274
+ Examples:
275
+ newo logs # Last 1 hour of logs
276
+ newo logs --hours 24 # Last 24 hours
277
+ newo logs --level warning,error # Warnings and errors only
278
+ newo logs --type call --skill CreateActor # Skill calls for CreateActor
279
+ newo logs --flow CACreatorFlow --follow # Tail logs for specific flow
280
+ newo logs --json --per 100 # Get 100 logs as JSON
281
+ `);
282
+ }
283
+ //# sourceMappingURL=logs.js.map
@@ -1,32 +1,136 @@
1
1
  /**
2
2
  * Pull command handler
3
+ *
4
+ * Supports selective resource sync with --only and --exclude flags:
5
+ * newo pull --only projects,attributes
6
+ * newo pull --exclude conversations,akb
7
+ * newo pull --all (explicit all resources)
8
+ *
9
+ * Available resources: projects, attributes, integrations, akb, conversations
3
10
  */
4
11
  import { makeClient } from '../../api.js';
5
12
  import { pullAll } from '../../sync.js';
6
13
  import { getValidAccessToken } from '../../auth.js';
7
14
  import { selectSingleCustomer } from '../customer-selection.js';
15
+ import { setupCli } from '../../cli-new/bootstrap.js';
16
+ import { ALL_RESOURCE_TYPES, RESOURCE_TYPES } from '../../cli-new/di/tokens.js';
17
+ /**
18
+ * Parse resource list from comma-separated string
19
+ */
20
+ function parseResourceList(input) {
21
+ if (!input)
22
+ return [];
23
+ return input.split(',').map(r => r.trim().toLowerCase()).filter(Boolean);
24
+ }
25
+ /**
26
+ * Validate resource types
27
+ */
28
+ function validateResources(resources) {
29
+ const validTypes = new Set(ALL_RESOURCE_TYPES);
30
+ const valid = [];
31
+ const invalid = [];
32
+ for (const r of resources) {
33
+ if (validTypes.has(r)) {
34
+ valid.push(r);
35
+ }
36
+ else {
37
+ invalid.push(r);
38
+ }
39
+ }
40
+ return { valid, invalid };
41
+ }
42
+ /**
43
+ * Pull using V2 SyncEngine with selective sync
44
+ */
45
+ async function pullWithV2Engine(customerConfig, customer, resources, verbose, silentOverwrite) {
46
+ const { syncEngine, logger } = setupCli(customerConfig, verbose);
47
+ const pullOptions = {
48
+ verbose,
49
+ silentOverwrite
50
+ };
51
+ if (resources === 'all') {
52
+ const result = await syncEngine.pullAll(customer, pullOptions);
53
+ logger.info(`āœ… Pulled ${result.totalItems} items from ${result.resources.length} resource types`);
54
+ if (result.errors.length > 0) {
55
+ logger.warn(`āš ļø ${result.errors.length} error(s) occurred`);
56
+ }
57
+ }
58
+ else {
59
+ const result = await syncEngine.pullSelected(customer, resources, pullOptions);
60
+ logger.info(`āœ… Pulled ${result.totalItems} items from ${result.resources.length} resource types`);
61
+ if (result.errors.length > 0) {
62
+ logger.warn(`āš ļø ${result.errors.length} error(s) occurred`);
63
+ }
64
+ }
65
+ }
8
66
  export async function handlePullCommand(customerConfig, args, verbose) {
9
67
  const { selectedCustomer, allCustomers, isMultiCustomer } = selectSingleCustomer(customerConfig, args.customer);
10
68
  // Check for force/silent overwrite flag
11
69
  const silentOverwrite = Boolean(args.force || args.f);
70
+ // Check for selective sync flags
71
+ const onlyResources = parseResourceList(args.only);
72
+ const excludeResources = parseResourceList(args.exclude);
73
+ const pullAllResources = Boolean(args.all);
74
+ // Validate resource types
75
+ if (onlyResources.length > 0) {
76
+ const { invalid } = validateResources(onlyResources);
77
+ if (invalid.length > 0) {
78
+ console.error(`āŒ Unknown resource type(s): ${invalid.join(', ')}`);
79
+ console.error(` Available: ${ALL_RESOURCE_TYPES.join(', ')}`);
80
+ process.exit(1);
81
+ }
82
+ }
83
+ if (excludeResources.length > 0) {
84
+ const { invalid } = validateResources(excludeResources);
85
+ if (invalid.length > 0) {
86
+ console.error(`āŒ Unknown resource type(s): ${invalid.join(', ')}`);
87
+ console.error(` Available: ${ALL_RESOURCE_TYPES.join(', ')}`);
88
+ process.exit(1);
89
+ }
90
+ }
91
+ // Determine which resources to pull
92
+ let resourcesToFetch = 'all';
93
+ if (onlyResources.length > 0) {
94
+ resourcesToFetch = onlyResources;
95
+ console.log(`šŸ“¦ Pulling selected resources: ${onlyResources.join(', ')}`);
96
+ }
97
+ else if (excludeResources.length > 0) {
98
+ resourcesToFetch = ALL_RESOURCE_TYPES.filter(r => !excludeResources.includes(r));
99
+ console.log(`šŸ“¦ Pulling resources (excluding: ${excludeResources.join(', ')})`);
100
+ }
101
+ else if (pullAllResources) {
102
+ resourcesToFetch = 'all';
103
+ console.log(`šŸ“¦ Pulling ALL resources`);
104
+ }
105
+ // Use V2 engine if selective sync requested, otherwise use legacy for backward compatibility
106
+ const useV2Engine = onlyResources.length > 0 || excludeResources.length > 0 || pullAllResources;
12
107
  if (selectedCustomer) {
13
- // Single customer pull
14
- const accessToken = await getValidAccessToken(selectedCustomer);
15
- const client = await makeClient(verbose, accessToken);
16
- const projectId = selectedCustomer.projectId || null;
17
- await pullAll(client, selectedCustomer, projectId, verbose, silentOverwrite);
108
+ if (useV2Engine) {
109
+ await pullWithV2Engine(customerConfig, selectedCustomer, resourcesToFetch, verbose, silentOverwrite);
110
+ }
111
+ else {
112
+ // Legacy behavior: pull projects + attributes only
113
+ const accessToken = await getValidAccessToken(selectedCustomer);
114
+ const client = await makeClient(verbose, accessToken);
115
+ const projectId = selectedCustomer.projectId || null;
116
+ await pullAll(client, selectedCustomer, projectId, verbose, silentOverwrite);
117
+ }
18
118
  }
19
119
  else if (isMultiCustomer) {
20
- // Multi-customer pull
21
120
  if (verbose)
22
121
  console.log(`šŸ“„ No default customer specified, pulling from all ${allCustomers.length} customers`);
23
122
  console.log(`šŸ”„ Pulling from ${allCustomers.length} customers...`);
24
123
  for (const customer of allCustomers) {
25
124
  console.log(`\nšŸ“„ Pulling from customer: ${customer.idn}`);
26
- const accessToken = await getValidAccessToken(customer);
27
- const client = await makeClient(verbose, accessToken);
28
- const projectId = customer.projectId || null;
29
- await pullAll(client, customer, projectId, verbose, silentOverwrite);
125
+ if (useV2Engine) {
126
+ await pullWithV2Engine(customerConfig, customer, resourcesToFetch, verbose, silentOverwrite);
127
+ }
128
+ else {
129
+ const accessToken = await getValidAccessToken(customer);
130
+ const client = await makeClient(verbose, accessToken);
131
+ const projectId = customer.projectId || null;
132
+ await pullAll(client, customer, projectId, verbose, silentOverwrite);
133
+ }
30
134
  }
31
135
  console.log(`\nāœ… Pull completed for all ${allCustomers.length} customers`);
32
136
  }
@@ -1,37 +1,147 @@
1
1
  /**
2
2
  * Push command handler
3
+ *
4
+ * Supports selective resource sync with --only and --exclude flags:
5
+ * newo push --only projects,attributes
6
+ * newo push --exclude integrations
7
+ * newo push --all (explicit all resources)
8
+ *
9
+ * Available resources: projects, attributes, integrations, akb
10
+ * Note: conversations is read-only and cannot be pushed
3
11
  */
4
12
  import { makeClient } from '../../api.js';
5
13
  import { pushChanged } from '../../sync.js';
6
14
  import { getValidAccessToken } from '../../auth.js';
7
15
  import { selectSingleCustomer, interactiveCustomerSelection } from '../customer-selection.js';
16
+ import { setupCli } from '../../cli-new/bootstrap.js';
17
+ import { PUSHABLE_RESOURCE_TYPES } from '../../cli-new/di/tokens.js';
18
+ /**
19
+ * Parse resource list from comma-separated string
20
+ */
21
+ function parseResourceList(input) {
22
+ if (!input)
23
+ return [];
24
+ return input.split(',').map(r => r.trim().toLowerCase()).filter(Boolean);
25
+ }
26
+ /**
27
+ * Validate resource types for push
28
+ */
29
+ function validateResources(resources) {
30
+ const validTypes = new Set(PUSHABLE_RESOURCE_TYPES);
31
+ const valid = [];
32
+ const invalid = [];
33
+ for (const r of resources) {
34
+ if (r === 'conversations') {
35
+ invalid.push(r + ' (read-only)');
36
+ }
37
+ else if (validTypes.has(r)) {
38
+ valid.push(r);
39
+ }
40
+ else {
41
+ invalid.push(r);
42
+ }
43
+ }
44
+ return { valid, invalid };
45
+ }
46
+ /**
47
+ * Push using V2 SyncEngine with selective sync
48
+ */
49
+ async function pushWithV2Engine(customerConfig, customer, resources, verbose) {
50
+ const { syncEngine, logger } = setupCli(customerConfig, verbose);
51
+ if (resources === 'all') {
52
+ const result = await syncEngine.pushAll(customer);
53
+ logger.info(`āœ… Pushed: ${result.totalCreated} created, ${result.totalUpdated} updated, ${result.totalDeleted} deleted`);
54
+ if (result.errors.length > 0) {
55
+ logger.warn(`āš ļø ${result.errors.length} error(s) occurred`);
56
+ result.errors.forEach(e => logger.error(` ${e}`));
57
+ }
58
+ }
59
+ else {
60
+ const result = await syncEngine.pushSelected(customer, resources);
61
+ logger.info(`āœ… Pushed: ${result.totalCreated} created, ${result.totalUpdated} updated, ${result.totalDeleted} deleted`);
62
+ if (result.errors.length > 0) {
63
+ logger.warn(`āš ļø ${result.errors.length} error(s) occurred`);
64
+ result.errors.forEach(e => logger.error(` ${e}`));
65
+ }
66
+ }
67
+ }
8
68
  export async function handlePushCommand(customerConfig, args, verbose) {
9
69
  const { selectedCustomer, allCustomers, isMultiCustomer } = selectSingleCustomer(customerConfig, args.customer);
10
70
  const shouldPublish = !args['no-publish'];
71
+ // Check for selective sync flags
72
+ const onlyResources = parseResourceList(args.only);
73
+ const excludeResources = parseResourceList(args.exclude);
74
+ const pushAllResources = Boolean(args.all);
75
+ // Validate resource types
76
+ if (onlyResources.length > 0) {
77
+ const { invalid } = validateResources(onlyResources);
78
+ if (invalid.length > 0) {
79
+ console.error(`āŒ Cannot push resource(s): ${invalid.join(', ')}`);
80
+ console.error(` Available for push: ${PUSHABLE_RESOURCE_TYPES.join(', ')}`);
81
+ process.exit(1);
82
+ }
83
+ }
84
+ if (excludeResources.length > 0) {
85
+ const { invalid } = validateResources(excludeResources);
86
+ if (invalid.length > 0 && !invalid.every(r => r.includes('read-only'))) {
87
+ console.error(`āŒ Unknown resource type(s): ${invalid.join(', ')}`);
88
+ console.error(` Available: ${PUSHABLE_RESOURCE_TYPES.join(', ')}`);
89
+ process.exit(1);
90
+ }
91
+ }
92
+ // Determine which resources to push
93
+ let resourcesToPush = 'all';
94
+ if (onlyResources.length > 0) {
95
+ resourcesToPush = onlyResources;
96
+ console.log(`šŸ“¦ Pushing selected resources: ${onlyResources.join(', ')}`);
97
+ }
98
+ else if (excludeResources.length > 0) {
99
+ resourcesToPush = PUSHABLE_RESOURCE_TYPES.filter(r => !excludeResources.includes(r));
100
+ console.log(`šŸ“¦ Pushing resources (excluding: ${excludeResources.join(', ')})`);
101
+ }
102
+ else if (pushAllResources) {
103
+ resourcesToPush = 'all';
104
+ console.log(`šŸ“¦ Pushing ALL resources`);
105
+ }
106
+ // Use V2 engine if selective sync requested, otherwise use legacy for backward compatibility
107
+ const useV2Engine = onlyResources.length > 0 || excludeResources.length > 0 || pushAllResources;
11
108
  if (selectedCustomer) {
12
- // Single customer push
13
- const accessToken = await getValidAccessToken(selectedCustomer);
14
- const client = await makeClient(verbose, accessToken);
15
- await pushChanged(client, selectedCustomer, verbose, shouldPublish);
109
+ if (useV2Engine) {
110
+ await pushWithV2Engine(customerConfig, selectedCustomer, resourcesToPush, verbose);
111
+ }
112
+ else {
113
+ // Legacy behavior
114
+ const accessToken = await getValidAccessToken(selectedCustomer);
115
+ const client = await makeClient(verbose, accessToken);
116
+ await pushChanged(client, selectedCustomer, verbose, shouldPublish);
117
+ }
16
118
  }
17
119
  else if (isMultiCustomer) {
18
120
  // Multiple customers exist with no default, ask user
19
121
  const customersToProcess = await interactiveCustomerSelection(allCustomers);
20
122
  if (customersToProcess.length === 1) {
21
- // Single customer selected
22
123
  const customer = customersToProcess[0];
23
- const accessToken = await getValidAccessToken(customer);
24
- const client = await makeClient(verbose, accessToken);
25
- await pushChanged(client, customer, verbose, shouldPublish);
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
+ }
26
132
  }
27
133
  else {
28
- // Multi-customer push (user selected "All customers")
29
134
  console.log(`šŸ”„ Pushing to ${customersToProcess.length} customers...`);
30
135
  for (const customer of customersToProcess) {
31
136
  console.log(`\nšŸ“¤ Pushing for customer: ${customer.idn}`);
32
- const accessToken = await getValidAccessToken(customer);
33
- const client = await makeClient(verbose, accessToken);
34
- await pushChanged(client, customer, verbose, shouldPublish);
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
+ }
35
145
  }
36
146
  console.log(`\nāœ… Push completed for all ${customersToProcess.length} customers`);
37
147
  }
@@ -0,0 +1,3 @@
1
+ import type { MultiCustomerConfig, CliArgs } from '../../types.js';
2
+ export declare function handleUpdateAttributeCommand(customerConfig: MultiCustomerConfig, args: CliArgs, verbose?: boolean): Promise<void>;
3
+ //# sourceMappingURL=update-attribute.d.ts.map
@@ -0,0 +1,78 @@
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
+ export async function handleUpdateAttributeCommand(customerConfig, args, verbose = false) {
8
+ try {
9
+ const selectedCustomer = requireSingleCustomer(customerConfig, args.customer);
10
+ // Parse arguments
11
+ const idn = args._[1];
12
+ if (!idn) {
13
+ console.error('Error: Attribute IDN is required');
14
+ console.error('Usage: newo update-attribute <idn> [--value <value>] [--title <title>] [--description <desc>] [--group <group>] [--hidden] [--value-type <type>] [--possible-values <val1,val2>]');
15
+ process.exit(1);
16
+ }
17
+ // Get access token and create client
18
+ const accessToken = await getValidAccessToken(selectedCustomer);
19
+ const client = await makeClient(verbose, accessToken);
20
+ // Fetch existing attributes to find the one to update
21
+ const response = await getCustomerAttributes(client, true);
22
+ const existing = response.attributes.find((a) => a.idn === idn);
23
+ if (!existing) {
24
+ console.error(`āŒ Attribute '${idn}' not found. Use 'newo create-attribute' to create it.`);
25
+ process.exit(1);
26
+ }
27
+ if (!existing.id) {
28
+ console.error(`āŒ Attribute '${idn}' has no ID. Cannot update.`);
29
+ process.exit(1);
30
+ }
31
+ // Build updated attribute - only override fields that were explicitly provided
32
+ const updated = {
33
+ id: existing.id,
34
+ idn: existing.idn,
35
+ value: args.value !== undefined ? String(args.value) : existing.value,
36
+ title: args.title || existing.title,
37
+ description: args.description || existing.description,
38
+ group: args.group || existing.group,
39
+ is_hidden: args.hidden !== undefined ? Boolean(args.hidden) : existing.is_hidden,
40
+ possible_values: args['possible-values']
41
+ ? args['possible-values'].split(',').map(v => v.trim())
42
+ : existing.possible_values,
43
+ value_type: args['value-type'] || existing.value_type
44
+ };
45
+ if (verbose) {
46
+ console.log(`šŸ“ Updating customer attribute: ${idn} (ID: ${existing.id})`);
47
+ if (args.value !== undefined)
48
+ console.log(` Value: ${existing.value} -> ${updated.value}`);
49
+ if (args.title)
50
+ console.log(` Title: ${existing.title} -> ${updated.title}`);
51
+ if (args.description)
52
+ console.log(` Description: updated`);
53
+ if (args.group)
54
+ console.log(` Group: ${existing.group} -> ${updated.group}`);
55
+ if (args['value-type'])
56
+ console.log(` Type: ${existing.value_type} -> ${updated.value_type}`);
57
+ if (args.hidden !== undefined)
58
+ console.log(` Hidden: ${existing.is_hidden} -> ${updated.is_hidden}`);
59
+ }
60
+ await updateCustomerAttribute(client, updated);
61
+ console.log(`āœ… Customer attribute updated: ${idn}`);
62
+ if (args.value !== undefined)
63
+ console.log(` Value: ${updated.value}`);
64
+ if (args['value-type'])
65
+ console.log(` Type: ${updated.value_type}`);
66
+ if (args.title)
67
+ console.log(` Title: ${updated.title}`);
68
+ if (args.description)
69
+ console.log(` Description: updated`);
70
+ if (args.group)
71
+ console.log(` Group: ${updated.group}`);
72
+ }
73
+ catch (error) {
74
+ console.error('āŒ Failed to update customer attribute:', error instanceof Error ? error.message : String(error));
75
+ process.exit(1);
76
+ }
77
+ }
78
+ //# sourceMappingURL=update-attribute.js.map
@@ -0,0 +1,6 @@
1
+ import type { MultiCustomerConfig, CliArgs } from '../../types.js';
2
+ /**
3
+ * Main watch command handler
4
+ */
5
+ export declare function handleWatchCommand(customerConfig: MultiCustomerConfig, args: CliArgs, verbose: boolean): Promise<void>;
6
+ //# sourceMappingURL=watch.d.ts.map