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.
- package/CHANGELOG.md +16 -0
- package/dist/api.d.ts +3 -1
- package/dist/api.js +49 -1
- package/dist/application/migration/MigrationEngine.d.ts +141 -0
- package/dist/application/migration/MigrationEngine.js +322 -0
- package/dist/application/migration/index.d.ts +5 -0
- package/dist/application/migration/index.js +5 -0
- package/dist/application/sync/SyncEngine.d.ts +134 -0
- package/dist/application/sync/SyncEngine.js +335 -0
- package/dist/application/sync/index.d.ts +5 -0
- package/dist/application/sync/index.js +5 -0
- package/dist/cli/commands/create-attribute.js +1 -1
- package/dist/cli/commands/create-customer.d.ts +3 -0
- package/dist/cli/commands/create-customer.js +159 -0
- package/dist/cli/commands/diff.d.ts +6 -0
- package/dist/cli/commands/diff.js +288 -0
- package/dist/cli/commands/help.js +63 -3
- package/dist/cli/commands/logs.d.ts +18 -0
- package/dist/cli/commands/logs.js +283 -0
- package/dist/cli/commands/pull.js +114 -10
- package/dist/cli/commands/push.js +122 -12
- package/dist/cli/commands/update-attribute.d.ts +3 -0
- package/dist/cli/commands/update-attribute.js +78 -0
- package/dist/cli/commands/watch.d.ts +6 -0
- package/dist/cli/commands/watch.js +195 -0
- package/dist/cli-new/bootstrap.d.ts +74 -0
- package/dist/cli-new/bootstrap.js +154 -0
- package/dist/cli-new/di/Container.d.ts +64 -0
- package/dist/cli-new/di/Container.js +122 -0
- package/dist/cli-new/di/tokens.d.ts +77 -0
- package/dist/cli-new/di/tokens.js +76 -0
- package/dist/cli.js +20 -0
- package/dist/domain/resources/common/types.d.ts +71 -0
- package/dist/domain/resources/common/types.js +42 -0
- package/dist/domain/strategies/sync/AkbSyncStrategy.d.ts +63 -0
- package/dist/domain/strategies/sync/AkbSyncStrategy.js +274 -0
- package/dist/domain/strategies/sync/AttributeSyncStrategy.d.ts +87 -0
- package/dist/domain/strategies/sync/AttributeSyncStrategy.js +378 -0
- package/dist/domain/strategies/sync/ConversationSyncStrategy.d.ts +61 -0
- package/dist/domain/strategies/sync/ConversationSyncStrategy.js +232 -0
- package/dist/domain/strategies/sync/ISyncStrategy.d.ts +149 -0
- package/dist/domain/strategies/sync/ISyncStrategy.js +24 -0
- package/dist/domain/strategies/sync/IntegrationSyncStrategy.d.ts +68 -0
- package/dist/domain/strategies/sync/IntegrationSyncStrategy.js +413 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +111 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.js +523 -0
- package/dist/domain/strategies/sync/index.d.ts +13 -0
- package/dist/domain/strategies/sync/index.js +19 -0
- package/dist/sync/migrate.js +99 -23
- package/dist/types.d.ts +124 -0
- package/package.json +3 -1
- package/src/api.ts +53 -2
- package/src/application/migration/MigrationEngine.ts +492 -0
- package/src/application/migration/index.ts +5 -0
- package/src/application/sync/SyncEngine.ts +467 -0
- package/src/application/sync/index.ts +5 -0
- package/src/cli/commands/create-attribute.ts +1 -1
- package/src/cli/commands/create-customer.ts +185 -0
- package/src/cli/commands/diff.ts +360 -0
- package/src/cli/commands/help.ts +63 -3
- package/src/cli/commands/logs.ts +329 -0
- package/src/cli/commands/pull.ts +128 -11
- package/src/cli/commands/push.ts +131 -13
- package/src/cli/commands/update-attribute.ts +82 -0
- package/src/cli/commands/watch.ts +227 -0
- package/src/cli-new/bootstrap.ts +252 -0
- package/src/cli-new/di/Container.ts +152 -0
- package/src/cli-new/di/tokens.ts +105 -0
- package/src/cli.ts +25 -0
- package/src/domain/resources/common/types.ts +106 -0
- package/src/domain/strategies/sync/AkbSyncStrategy.ts +358 -0
- package/src/domain/strategies/sync/AttributeSyncStrategy.ts +508 -0
- package/src/domain/strategies/sync/ConversationSyncStrategy.ts +299 -0
- package/src/domain/strategies/sync/ISyncStrategy.ts +182 -0
- package/src/domain/strategies/sync/IntegrationSyncStrategy.ts +522 -0
- package/src/domain/strategies/sync/ProjectSyncStrategy.ts +747 -0
- package/src/domain/strategies/sync/index.ts +46 -0
- package/src/sync/migrate.ts +103 -24
- 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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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,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
|