newo 3.3.3 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/api.d.ts +6 -1
  3. package/dist/api.js +63 -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/add-project.d.ts +3 -0
  13. package/dist/cli/commands/add-project.js +136 -0
  14. package/dist/cli/commands/create-customer.d.ts +3 -0
  15. package/dist/cli/commands/create-customer.js +159 -0
  16. package/dist/cli/commands/diff.d.ts +6 -0
  17. package/dist/cli/commands/diff.js +288 -0
  18. package/dist/cli/commands/help.js +75 -4
  19. package/dist/cli/commands/list-registries.d.ts +3 -0
  20. package/dist/cli/commands/list-registries.js +39 -0
  21. package/dist/cli/commands/list-registry-items.d.ts +3 -0
  22. package/dist/cli/commands/list-registry-items.js +112 -0
  23. package/dist/cli/commands/logs.d.ts +18 -0
  24. package/dist/cli/commands/logs.js +283 -0
  25. package/dist/cli/commands/pull.js +114 -10
  26. package/dist/cli/commands/push.js +122 -12
  27. package/dist/cli/commands/watch.d.ts +6 -0
  28. package/dist/cli/commands/watch.js +195 -0
  29. package/dist/cli-new/bootstrap.d.ts +74 -0
  30. package/dist/cli-new/bootstrap.js +154 -0
  31. package/dist/cli-new/di/Container.d.ts +64 -0
  32. package/dist/cli-new/di/Container.js +122 -0
  33. package/dist/cli-new/di/tokens.d.ts +77 -0
  34. package/dist/cli-new/di/tokens.js +76 -0
  35. package/dist/cli.js +28 -0
  36. package/dist/domain/resources/common/types.d.ts +71 -0
  37. package/dist/domain/resources/common/types.js +42 -0
  38. package/dist/domain/strategies/sync/AkbSyncStrategy.d.ts +63 -0
  39. package/dist/domain/strategies/sync/AkbSyncStrategy.js +274 -0
  40. package/dist/domain/strategies/sync/AttributeSyncStrategy.d.ts +87 -0
  41. package/dist/domain/strategies/sync/AttributeSyncStrategy.js +378 -0
  42. package/dist/domain/strategies/sync/ConversationSyncStrategy.d.ts +61 -0
  43. package/dist/domain/strategies/sync/ConversationSyncStrategy.js +232 -0
  44. package/dist/domain/strategies/sync/ISyncStrategy.d.ts +149 -0
  45. package/dist/domain/strategies/sync/ISyncStrategy.js +24 -0
  46. package/dist/domain/strategies/sync/IntegrationSyncStrategy.d.ts +68 -0
  47. package/dist/domain/strategies/sync/IntegrationSyncStrategy.js +413 -0
  48. package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +111 -0
  49. package/dist/domain/strategies/sync/ProjectSyncStrategy.js +523 -0
  50. package/dist/domain/strategies/sync/index.d.ts +13 -0
  51. package/dist/domain/strategies/sync/index.js +19 -0
  52. package/dist/sync/migrate.js +99 -23
  53. package/dist/types.d.ts +162 -0
  54. package/package.json +3 -1
  55. package/src/api.ts +77 -2
  56. package/src/application/migration/MigrationEngine.ts +492 -0
  57. package/src/application/migration/index.ts +5 -0
  58. package/src/application/sync/SyncEngine.ts +467 -0
  59. package/src/application/sync/index.ts +5 -0
  60. package/src/cli/commands/add-project.ts +159 -0
  61. package/src/cli/commands/create-customer.ts +185 -0
  62. package/src/cli/commands/diff.ts +360 -0
  63. package/src/cli/commands/help.ts +75 -4
  64. package/src/cli/commands/list-registries.ts +53 -0
  65. package/src/cli/commands/list-registry-items.ts +149 -0
  66. package/src/cli/commands/logs.ts +329 -0
  67. package/src/cli/commands/pull.ts +128 -11
  68. package/src/cli/commands/push.ts +131 -13
  69. package/src/cli/commands/watch.ts +227 -0
  70. package/src/cli-new/bootstrap.ts +252 -0
  71. package/src/cli-new/di/Container.ts +152 -0
  72. package/src/cli-new/di/tokens.ts +105 -0
  73. package/src/cli.ts +35 -0
  74. package/src/domain/resources/common/types.ts +106 -0
  75. package/src/domain/strategies/sync/AkbSyncStrategy.ts +358 -0
  76. package/src/domain/strategies/sync/AttributeSyncStrategy.ts +508 -0
  77. package/src/domain/strategies/sync/ConversationSyncStrategy.ts +299 -0
  78. package/src/domain/strategies/sync/ISyncStrategy.ts +182 -0
  79. package/src/domain/strategies/sync/IntegrationSyncStrategy.ts +522 -0
  80. package/src/domain/strategies/sync/ProjectSyncStrategy.ts +747 -0
  81. package/src/domain/strategies/sync/index.ts +46 -0
  82. package/src/sync/migrate.ts +103 -24
  83. package/src/types.ts +178 -0
@@ -0,0 +1,53 @@
1
+ /**
2
+ * List Registries Command Handler - Lists available project registries
3
+ */
4
+ import { makeClient, listRegistries } from '../../api.js';
5
+ import { getValidAccessToken } from '../../auth.js';
6
+ import { requireSingleCustomer } from '../customer-selection.js';
7
+ import type { MultiCustomerConfig, CliArgs } from '../../types.js';
8
+
9
+ export async function handleListRegistriesCommand(
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
+ if (verbose) {
18
+ console.log(`📋 Fetching registries for customer: ${selectedCustomer.idn}`);
19
+ }
20
+
21
+ // Get access token and create client
22
+ const accessToken = await getValidAccessToken(selectedCustomer);
23
+ const client = await makeClient(verbose, accessToken);
24
+
25
+ console.log('🔍 Fetching available project registries...\n');
26
+
27
+ const registries = await listRegistries(client);
28
+
29
+ if (registries.length === 0) {
30
+ console.log('No registries found.');
31
+ return;
32
+ }
33
+
34
+ console.log(`✅ Found ${registries.length} registries:\n`);
35
+
36
+ // Display registries in a table-like format
37
+ console.log(' IDN │ Public │ ID');
38
+ console.log(' ────────────────────────┼────────┼────────────────────────────────────');
39
+
40
+ for (const registry of registries) {
41
+ const publicStatus = registry.is_public ? 'Yes' : 'No';
42
+ const idnPadded = registry.idn.padEnd(22);
43
+ const publicPadded = publicStatus.padEnd(6);
44
+ console.log(` ${idnPadded} │ ${publicPadded} │ ${registry.id}`);
45
+ }
46
+
47
+ console.log('\n💡 Use "newo list-registry-items <registry-idn>" to see available projects in a registry');
48
+
49
+ } catch (error: unknown) {
50
+ console.error('❌ Failed to list registries:', error instanceof Error ? error.message : String(error));
51
+ process.exit(1);
52
+ }
53
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * List Registry Items Command Handler - Lists available projects in a registry
3
+ */
4
+ import { makeClient, listRegistries, listRegistryItems } from '../../api.js';
5
+ import { getValidAccessToken } from '../../auth.js';
6
+ import { requireSingleCustomer } from '../customer-selection.js';
7
+ import type { MultiCustomerConfig, CliArgs, Registry, RegistryItem } from '../../types.js';
8
+
9
+ interface GroupedItem {
10
+ idn: string;
11
+ versions: RegistryItem[];
12
+ latestVersion: string;
13
+ totalActiveProjects: number;
14
+ }
15
+
16
+ function groupItemsByIdn(items: RegistryItem[]): GroupedItem[] {
17
+ const groups = new Map<string, RegistryItem[]>();
18
+
19
+ for (const item of items) {
20
+ const existing = groups.get(item.idn) || [];
21
+ existing.push(item);
22
+ groups.set(item.idn, existing);
23
+ }
24
+
25
+ const result: GroupedItem[] = [];
26
+
27
+ for (const [idn, versions] of groups) {
28
+ // Sort versions by published_at descending (newest first)
29
+ const sortedVersions = [...versions].sort((a, b) =>
30
+ new Date(b.published_at).getTime() - new Date(a.published_at).getTime()
31
+ );
32
+
33
+ const latestItem = sortedVersions[0];
34
+ if (!latestItem) continue;
35
+
36
+ // Sum up active project counts across all versions
37
+ const totalActiveProjects = versions.reduce((sum, v) => sum + v.active_project_count, 0);
38
+
39
+ result.push({
40
+ idn,
41
+ versions: sortedVersions,
42
+ latestVersion: latestItem.version,
43
+ totalActiveProjects
44
+ });
45
+ }
46
+
47
+ // Sort by IDN alphabetically
48
+ return result.sort((a, b) => a.idn.localeCompare(b.idn));
49
+ }
50
+
51
+ export async function handleListRegistryItemsCommand(
52
+ customerConfig: MultiCustomerConfig,
53
+ args: CliArgs,
54
+ verbose: boolean = false
55
+ ): Promise<void> {
56
+ try {
57
+ const selectedCustomer = requireSingleCustomer(customerConfig, args.customer as string | undefined);
58
+
59
+ // Parse arguments
60
+ const registryIdn = args._[1] as string;
61
+ const showAllVersions = Boolean(args.all || args.a);
62
+
63
+ if (!registryIdn) {
64
+ console.error('Error: Registry IDN is required');
65
+ console.error('Usage: newo list-registry-items <registry-idn> [--all]');
66
+ console.error('');
67
+ console.error('Examples:');
68
+ console.error(' newo list-registry-items production');
69
+ console.error(' newo list-registry-items staging --all');
70
+ console.error('');
71
+ console.error('Run "newo list-registries" to see available registries');
72
+ process.exit(1);
73
+ }
74
+
75
+ if (verbose) {
76
+ console.log(`📋 Fetching items from registry: ${registryIdn}`);
77
+ console.log(` Customer: ${selectedCustomer.idn}`);
78
+ console.log(` Show all versions: ${showAllVersions}`);
79
+ }
80
+
81
+ // Get access token and create client
82
+ const accessToken = await getValidAccessToken(selectedCustomer);
83
+ const client = await makeClient(verbose, accessToken);
84
+
85
+ // First, get registries to find the ID
86
+ console.log(`🔍 Fetching registry "${registryIdn}"...`);
87
+ const registries = await listRegistries(client);
88
+ const registry = registries.find((r: Registry) => r.idn === registryIdn);
89
+
90
+ if (!registry) {
91
+ console.error(`❌ Registry "${registryIdn}" not found`);
92
+ console.error('');
93
+ console.error('Available registries:');
94
+ for (const r of registries) {
95
+ console.error(` • ${r.idn}`);
96
+ }
97
+ process.exit(1);
98
+ }
99
+
100
+ console.log(`📦 Fetching projects from "${registryIdn}" registry (this may take a moment)...\n`);
101
+
102
+ const items = await listRegistryItems(client, registry.id);
103
+
104
+ if (items.length === 0) {
105
+ console.log(`No projects found in "${registryIdn}" registry.`);
106
+ return;
107
+ }
108
+
109
+ if (showAllVersions) {
110
+ // Show all versions
111
+ console.log(`✅ Found ${items.length} project versions in "${registryIdn}" registry:\n`);
112
+
113
+ console.log(' Project IDN │ Version │ Active │ Published');
114
+ console.log(' ───────────────────────┼──────────┼────────┼────────────────────');
115
+
116
+ for (const item of items) {
117
+ const idnPadded = item.idn.substring(0, 21).padEnd(21);
118
+ const versionPadded = item.version.substring(0, 8).padEnd(8);
119
+ const activePadded = String(item.active_project_count).padEnd(6);
120
+ const published = new Date(item.published_at).toISOString().split('T')[0];
121
+ console.log(` ${idnPadded} │ ${versionPadded} │ ${activePadded} │ ${published}`);
122
+ }
123
+ } else {
124
+ // Group by project IDN and show only latest version
125
+ const grouped = groupItemsByIdn(items);
126
+
127
+ console.log(`✅ Found ${grouped.length} unique projects in "${registryIdn}" registry:\n`);
128
+
129
+ console.log(' Project IDN │ Latest │ Active │ Versions');
130
+ console.log(' ───────────────────────┼──────────┼────────┼──────────');
131
+
132
+ for (const group of grouped) {
133
+ const idnPadded = group.idn.substring(0, 21).padEnd(21);
134
+ const versionPadded = group.latestVersion.substring(0, 8).padEnd(8);
135
+ const activePadded = String(group.totalActiveProjects).padEnd(6);
136
+ const versionCount = String(group.versions.length).padEnd(8);
137
+ console.log(` ${idnPadded} │ ${versionPadded} │ ${activePadded} │ ${versionCount}`);
138
+ }
139
+
140
+ console.log('\n💡 Use --all flag to see all versions');
141
+ }
142
+
143
+ console.log(`\n💡 Use "newo add-project <idn> --registry ${registryIdn} --item <project-idn>" to install a project`);
144
+
145
+ } catch (error: unknown) {
146
+ console.error('❌ Failed to list registry items:', error instanceof Error ? error.message : String(error));
147
+ process.exit(1);
148
+ }
149
+ }
@@ -0,0 +1,329 @@
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
+
16
+ import type { AxiosInstance } from 'axios';
17
+ import type { MultiCustomerConfig, LogEntry, LogLevel, LogType, LogsQueryParams, CliArgs } from '../../types.js';
18
+ import { makeClient, getLogs } from '../../api.js';
19
+ import { getValidAccessToken } from '../../auth.js';
20
+ import { requireSingleCustomer } from '../customer-selection.js';
21
+
22
+ // ANSI color codes for terminal output
23
+ const colors = {
24
+ reset: '\x1b[0m',
25
+ dim: '\x1b[2m',
26
+ red: '\x1b[31m',
27
+ yellow: '\x1b[33m',
28
+ blue: '\x1b[34m',
29
+ cyan: '\x1b[36m',
30
+ green: '\x1b[32m',
31
+ magenta: '\x1b[35m',
32
+ white: '\x1b[37m',
33
+ gray: '\x1b[90m'
34
+ };
35
+
36
+ function getLevelColor(level: LogLevel): string {
37
+ switch (level) {
38
+ case 'error': return colors.red;
39
+ case 'warning': return colors.yellow;
40
+ case 'info': return colors.blue;
41
+ default: return colors.white;
42
+ }
43
+ }
44
+
45
+ function getTypeColor(type: LogType): string {
46
+ switch (type) {
47
+ case 'call': return colors.cyan;
48
+ case 'operation': return colors.magenta;
49
+ case 'system': return colors.green;
50
+ default: return colors.white;
51
+ }
52
+ }
53
+
54
+ function formatLogEntry(log: LogEntry, showColors: boolean = true): string {
55
+ const c = showColors ? colors : { reset: '', dim: '', red: '', yellow: '', blue: '', cyan: '', green: '', magenta: '', white: '', gray: '' };
56
+
57
+ // Format datetime
58
+ const date = new Date(log.datetime);
59
+ const timeStr = date.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' });
60
+ const msStr = String(date.getMilliseconds()).padStart(3, '0');
61
+ const dateTimeFormatted = `${c.dim}${timeStr}.${msStr}${c.reset}`;
62
+
63
+ // Format level with color
64
+ const levelColor = showColors ? getLevelColor(log.level) : '';
65
+ const levelStr = `${levelColor}${log.level.toUpperCase().padEnd(7)}${c.reset}`;
66
+
67
+ // Format type with color
68
+ const typeColor = showColors ? getTypeColor(log.log_type) : '';
69
+ const typeStr = `${typeColor}${log.log_type.padEnd(9)}${c.reset}`;
70
+
71
+ // Build context string from data
72
+ const contextParts: string[] = [];
73
+ if (log.data.flow_idn) contextParts.push(`${c.cyan}[${log.data.flow_idn}]${c.reset}`);
74
+ if (log.data.skill_idn) contextParts.push(`${c.green}[${log.data.skill_idn}]${c.reset}`);
75
+ if (log.data.line !== undefined) contextParts.push(`${c.dim}:${log.data.line}${c.reset}`);
76
+ if (log.data.integration_idn && log.data.connector_idn) {
77
+ contextParts.push(`${c.magenta}${log.data.integration_idn}/${log.data.connector_idn}${c.reset}`);
78
+ }
79
+
80
+ const contextStr = contextParts.length > 0 ? ` ${contextParts.join(' ')}` : '';
81
+
82
+ // Format message
83
+ const messageStr = log.message;
84
+
85
+ return `${dateTimeFormatted} ${levelStr} ${typeStr}${contextStr} ${messageStr}`;
86
+ }
87
+
88
+ function formatLogEntryCompact(log: LogEntry, showColors: boolean = true): string {
89
+ const c = showColors ? colors : { reset: '', dim: '', red: '', yellow: '', blue: '', cyan: '', green: '', magenta: '', white: '', gray: '' };
90
+
91
+ // Shorter format for tail mode
92
+ const date = new Date(log.datetime);
93
+ const timeStr = date.toLocaleTimeString('en-US', { hour12: false });
94
+
95
+ const levelColor = showColors ? getLevelColor(log.level) : '';
96
+ const levelIcon = log.level === 'error' ? '✗' : log.level === 'warning' ? '⚠' : '•';
97
+
98
+ return `${c.dim}${timeStr}${c.reset} ${levelColor}${levelIcon}${c.reset} ${log.message}`;
99
+ }
100
+
101
+ export async function handleLogsCommand(
102
+ customerConfig: MultiCustomerConfig,
103
+ args: CliArgs,
104
+ verbose: boolean
105
+ ): Promise<void> {
106
+ // Select customer
107
+ const selectedCustomer = requireSingleCustomer(customerConfig, args.customer as string | undefined);
108
+
109
+ console.log(`📊 Fetching logs for ${selectedCustomer.idn}...`);
110
+
111
+ // Get access token and create client
112
+ const token = await getValidAccessToken(selectedCustomer);
113
+ const client = await makeClient(verbose, token);
114
+
115
+ // Build query params
116
+ const params: LogsQueryParams = {
117
+ page: args.page ? parseInt(String(args.page), 10) : 1,
118
+ per: args.per ? parseInt(String(args.per), 10) : 50
119
+ };
120
+
121
+ // Time range
122
+ if (args.from) {
123
+ params.from_datetime = String(args.from);
124
+ } else {
125
+ // Default to last N hours (default 1 hour)
126
+ const hoursAgo = args.hours ? parseInt(String(args.hours), 10) : 1;
127
+ params.from_datetime = new Date(Date.now() - hoursAgo * 60 * 60 * 1000).toISOString();
128
+ }
129
+
130
+ if (args.to) {
131
+ params.to_datetime = String(args.to);
132
+ }
133
+
134
+ // Filters
135
+ if (args.level) {
136
+ const levelStr = String(args.level);
137
+ const levels = levelStr.split(',') as LogLevel[];
138
+ if (levels.length === 1 && levels[0]) {
139
+ params.levels = levels[0];
140
+ } else if (levels.length > 1) {
141
+ params.levels = levels;
142
+ }
143
+ }
144
+
145
+ if (args.type) {
146
+ const typeStr = String(args.type);
147
+ const types = typeStr.split(',') as LogType[];
148
+ if (types.length === 1 && types[0]) {
149
+ params.log_types = types[0];
150
+ } else if (types.length > 1) {
151
+ params.log_types = types;
152
+ }
153
+ }
154
+
155
+ if (args.project) params.project_idn = String(args.project);
156
+ if (args.flow) params.flow_idn = String(args.flow);
157
+ if (args.skill) params.skill_idn = String(args.skill);
158
+ if (args.message) params.message = String(args.message);
159
+ if (args['event-id']) params.external_event_id = String(args['event-id']);
160
+ if (args['runtime-id']) params.runtime_context_id = String(args['runtime-id']);
161
+ if (args['actor-id']) params.user_actor_ids = String(args['actor-id']);
162
+ if (args['persona-id']) params.user_persona_ids = String(args['persona-id']);
163
+
164
+ const follow = Boolean(args.follow || args.f);
165
+ const asJson = Boolean(args.json);
166
+ const raw = Boolean(args.raw);
167
+
168
+ if (follow) {
169
+ await tailLogs(client, params, asJson);
170
+ } else {
171
+ await fetchAndDisplayLogs(client, params, asJson, raw);
172
+ }
173
+ }
174
+
175
+ async function fetchAndDisplayLogs(
176
+ client: AxiosInstance,
177
+ params: LogsQueryParams,
178
+ asJson: boolean,
179
+ raw: boolean
180
+ ): Promise<void> {
181
+ try {
182
+ const response = await getLogs(client, params);
183
+ const logs = response.items;
184
+
185
+ if (asJson) {
186
+ console.log(JSON.stringify(logs, null, 2));
187
+ return;
188
+ }
189
+
190
+ if (logs.length === 0) {
191
+ console.log('\nNo logs found for the specified criteria.');
192
+ return;
193
+ }
194
+
195
+ console.log(`\n📝 Found ${logs.length} log entries:\n`);
196
+
197
+ // Sort by datetime ascending (oldest first)
198
+ const sortedLogs = [...logs].sort((a, b) =>
199
+ new Date(a.datetime).getTime() - new Date(b.datetime).getTime()
200
+ );
201
+
202
+ // Detect if stdout is a TTY (supports colors)
203
+ const useColors = process.stdout.isTTY !== false;
204
+
205
+ for (const log of sortedLogs) {
206
+ if (raw) {
207
+ console.log(JSON.stringify(log));
208
+ } else {
209
+ console.log(formatLogEntry(log, useColors));
210
+ }
211
+ }
212
+
213
+ console.log(`\n✅ Displayed ${logs.length} log entries`);
214
+ } catch (error: unknown) {
215
+ const err = error as { response?: { status?: number; data?: unknown }; message?: string };
216
+ console.error('Failed to fetch logs:', err.response?.status, err.response?.data || err.message);
217
+ }
218
+ }
219
+
220
+ async function tailLogs(
221
+ client: AxiosInstance,
222
+ params: LogsQueryParams,
223
+ asJson: boolean
224
+ ): Promise<void> {
225
+ console.log('🔄 Watching for new logs (Ctrl+C to stop)...\n');
226
+
227
+ const seenLogIds = new Set<string>();
228
+ let lastCheckTime = params.from_datetime || new Date(Date.now() - 60 * 60 * 1000).toISOString();
229
+ const useColors = process.stdout.isTTY !== false;
230
+
231
+ // Poll interval (2 seconds)
232
+ const pollInterval = 2000;
233
+
234
+ const poll = async () => {
235
+ try {
236
+ const pollParams: LogsQueryParams = {
237
+ ...params,
238
+ from_datetime: lastCheckTime,
239
+ page: 1,
240
+ per: 100
241
+ };
242
+
243
+ const response = await getLogs(client, pollParams);
244
+ const logs = response.items;
245
+
246
+ // Filter out already seen logs and sort by time
247
+ const newLogs = logs
248
+ .filter(log => !seenLogIds.has(log.log_id))
249
+ .sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime());
250
+
251
+ for (const log of newLogs) {
252
+ seenLogIds.add(log.log_id);
253
+
254
+ if (asJson) {
255
+ console.log(JSON.stringify(log));
256
+ } else {
257
+ console.log(formatLogEntryCompact(log, useColors));
258
+ }
259
+
260
+ // Update last check time to the newest log we've seen
261
+ const logTime = new Date(log.datetime);
262
+ const lastTime = new Date(lastCheckTime);
263
+ if (logTime > lastTime) {
264
+ lastCheckTime = log.datetime;
265
+ }
266
+ }
267
+ } catch (error: unknown) {
268
+ // Silently ignore poll errors to avoid spamming the console
269
+ }
270
+ };
271
+
272
+ // Initial poll
273
+ await poll();
274
+
275
+ // Set up interval for continuous polling
276
+ const intervalId = setInterval(poll, pollInterval);
277
+
278
+ // Handle Ctrl+C gracefully
279
+ process.on('SIGINT', () => {
280
+ clearInterval(intervalId);
281
+ console.log('\n\n👋 Stopped watching logs');
282
+ process.exit(0);
283
+ });
284
+
285
+ // Keep the process running
286
+ await new Promise(() => {});
287
+ }
288
+
289
+ export function printLogsHelp(): void {
290
+ console.log(`
291
+ Usage: newo logs [options]
292
+
293
+ Fetch and display analytics logs from the NEWO platform.
294
+
295
+ Time Range Options:
296
+ --hours <n> Show logs from last N hours (default: 1)
297
+ --from <datetime> Start datetime (ISO format, e.g., 2026-01-11T00:00:00Z)
298
+ --to <datetime> End datetime (ISO format)
299
+
300
+ Filter Options:
301
+ --level <levels> Filter by log level: info, warning, error (comma-separated)
302
+ --type <types> Filter by log type: system, operation, call (comma-separated)
303
+ --project <idn> Filter by project IDN
304
+ --flow <idn> Filter by flow IDN
305
+ --skill <idn> Filter by skill IDN
306
+ --message <text> Search in log messages
307
+ --event-id <uuid> Filter by external event ID
308
+ --runtime-id <uuid> Filter by runtime context ID
309
+ --actor-id <uuid> Filter by user actor ID
310
+ --persona-id <uuid> Filter by user persona ID
311
+
312
+ Output Options:
313
+ --json Output logs as JSON
314
+ --raw Output each log as a single JSON line
315
+ --per <n> Number of logs per page (default: 50)
316
+ --page <n> Page number (default: 1)
317
+
318
+ Live Tailing:
319
+ --follow, -f Continuously poll for new logs (like tail -f)
320
+
321
+ Examples:
322
+ newo logs # Last 1 hour of logs
323
+ newo logs --hours 24 # Last 24 hours
324
+ newo logs --level warning,error # Warnings and errors only
325
+ newo logs --type call --skill CreateActor # Skill calls for CreateActor
326
+ newo logs --flow CACreatorFlow --follow # Tail logs for specific flow
327
+ newo logs --json --per 100 # Get 100 logs as JSON
328
+ `);
329
+ }
@@ -1,11 +1,79 @@
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';
8
- import type { MultiCustomerConfig, CliArgs } from '../../types.js';
15
+ import { setupCli } from '../../cli-new/bootstrap.js';
16
+ import { ALL_RESOURCE_TYPES, RESOURCE_TYPES } from '../../cli-new/di/tokens.js';
17
+ import type { MultiCustomerConfig, CliArgs, CustomerConfig } from '../../types.js';
18
+
19
+ /**
20
+ * Parse resource list from comma-separated string
21
+ */
22
+ function parseResourceList(input: string | undefined): string[] {
23
+ if (!input) return [];
24
+ return input.split(',').map(r => r.trim().toLowerCase()).filter(Boolean);
25
+ }
26
+
27
+ /**
28
+ * Validate resource types
29
+ */
30
+ function validateResources(resources: string[]): { valid: string[]; invalid: string[] } {
31
+ const validTypes = new Set(ALL_RESOURCE_TYPES);
32
+ const valid: string[] = [];
33
+ const invalid: string[] = [];
34
+
35
+ for (const r of resources) {
36
+ if (validTypes.has(r as typeof RESOURCE_TYPES[keyof typeof RESOURCE_TYPES])) {
37
+ valid.push(r);
38
+ } else {
39
+ invalid.push(r);
40
+ }
41
+ }
42
+
43
+ return { valid, invalid };
44
+ }
45
+
46
+ /**
47
+ * Pull using V2 SyncEngine with selective sync
48
+ */
49
+ async function pullWithV2Engine(
50
+ customerConfig: MultiCustomerConfig,
51
+ customer: CustomerConfig,
52
+ resources: string[] | 'all',
53
+ verbose: boolean,
54
+ silentOverwrite: boolean
55
+ ): Promise<void> {
56
+ const { syncEngine, logger } = setupCli(customerConfig, verbose);
57
+
58
+ const pullOptions = {
59
+ verbose,
60
+ silentOverwrite
61
+ };
62
+
63
+ if (resources === 'all') {
64
+ const result = await syncEngine.pullAll(customer, pullOptions);
65
+ logger.info(`✅ Pulled ${result.totalItems} items from ${result.resources.length} resource types`);
66
+ if (result.errors.length > 0) {
67
+ logger.warn(`⚠️ ${result.errors.length} error(s) occurred`);
68
+ }
69
+ } else {
70
+ const result = await syncEngine.pullSelected(customer, resources, pullOptions);
71
+ logger.info(`✅ Pulled ${result.totalItems} items from ${result.resources.length} resource types`);
72
+ if (result.errors.length > 0) {
73
+ logger.warn(`⚠️ ${result.errors.length} error(s) occurred`);
74
+ }
75
+ }
76
+ }
9
77
 
10
78
  export async function handlePullCommand(
11
79
  customerConfig: MultiCustomerConfig,
@@ -20,23 +88,72 @@ export async function handlePullCommand(
20
88
  // Check for force/silent overwrite flag
21
89
  const silentOverwrite = Boolean(args.force || args.f);
22
90
 
91
+ // Check for selective sync flags
92
+ const onlyResources = parseResourceList(args.only as string | undefined);
93
+ const excludeResources = parseResourceList(args.exclude as string | undefined);
94
+ const pullAllResources = Boolean(args.all);
95
+
96
+ // Validate resource types
97
+ if (onlyResources.length > 0) {
98
+ const { invalid } = validateResources(onlyResources);
99
+ if (invalid.length > 0) {
100
+ console.error(`❌ Unknown resource type(s): ${invalid.join(', ')}`);
101
+ console.error(` Available: ${ALL_RESOURCE_TYPES.join(', ')}`);
102
+ process.exit(1);
103
+ }
104
+ }
105
+
106
+ if (excludeResources.length > 0) {
107
+ const { invalid } = validateResources(excludeResources);
108
+ if (invalid.length > 0) {
109
+ console.error(`❌ Unknown resource type(s): ${invalid.join(', ')}`);
110
+ console.error(` Available: ${ALL_RESOURCE_TYPES.join(', ')}`);
111
+ process.exit(1);
112
+ }
113
+ }
114
+
115
+ // Determine which resources to pull
116
+ let resourcesToFetch: string[] | 'all' = 'all';
117
+
118
+ if (onlyResources.length > 0) {
119
+ resourcesToFetch = onlyResources;
120
+ console.log(`📦 Pulling selected resources: ${onlyResources.join(', ')}`);
121
+ } else if (excludeResources.length > 0) {
122
+ resourcesToFetch = ALL_RESOURCE_TYPES.filter(r => !excludeResources.includes(r));
123
+ console.log(`📦 Pulling resources (excluding: ${excludeResources.join(', ')})`);
124
+ } else if (pullAllResources) {
125
+ resourcesToFetch = 'all';
126
+ console.log(`📦 Pulling ALL resources`);
127
+ }
128
+
129
+ // Use V2 engine if selective sync requested, otherwise use legacy for backward compatibility
130
+ const useV2Engine = onlyResources.length > 0 || excludeResources.length > 0 || pullAllResources;
131
+
23
132
  if (selectedCustomer) {
24
- // Single customer pull
25
- const accessToken = await getValidAccessToken(selectedCustomer);
26
- const client = await makeClient(verbose, accessToken);
27
- const projectId = selectedCustomer.projectId || null;
28
- await pullAll(client, selectedCustomer, projectId, verbose, silentOverwrite);
133
+ if (useV2Engine) {
134
+ await pullWithV2Engine(customerConfig, selectedCustomer, resourcesToFetch, verbose, silentOverwrite);
135
+ } else {
136
+ // Legacy behavior: pull projects + attributes only
137
+ const accessToken = await getValidAccessToken(selectedCustomer);
138
+ const client = await makeClient(verbose, accessToken);
139
+ const projectId = selectedCustomer.projectId || null;
140
+ await pullAll(client, selectedCustomer, projectId, verbose, silentOverwrite);
141
+ }
29
142
  } else if (isMultiCustomer) {
30
- // Multi-customer pull
31
143
  if (verbose) console.log(`📥 No default customer specified, pulling from all ${allCustomers.length} customers`);
32
144
  console.log(`🔄 Pulling from ${allCustomers.length} customers...`);
33
145
 
34
146
  for (const customer of allCustomers) {
35
147
  console.log(`\n📥 Pulling from customer: ${customer.idn}`);
36
- const accessToken = await getValidAccessToken(customer);
37
- const client = await makeClient(verbose, accessToken);
38
- const projectId = customer.projectId || null;
39
- await pullAll(client, customer, projectId, verbose, silentOverwrite);
148
+
149
+ if (useV2Engine) {
150
+ await pullWithV2Engine(customerConfig, customer, resourcesToFetch, verbose, silentOverwrite);
151
+ } else {
152
+ const accessToken = await getValidAccessToken(customer);
153
+ const client = await makeClient(verbose, accessToken);
154
+ const projectId = customer.projectId || null;
155
+ await pullAll(client, customer, projectId, verbose, silentOverwrite);
156
+ }
40
157
  }
41
158
  console.log(`\n✅ Pull completed for all ${allCustomers.length} customers`);
42
159
  }