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.
- package/CHANGELOG.md +41 -0
- package/dist/api.d.ts +6 -1
- package/dist/api.js +63 -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/add-project.d.ts +3 -0
- package/dist/cli/commands/add-project.js +136 -0
- 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 +75 -4
- package/dist/cli/commands/list-registries.d.ts +3 -0
- package/dist/cli/commands/list-registries.js +39 -0
- package/dist/cli/commands/list-registry-items.d.ts +3 -0
- package/dist/cli/commands/list-registry-items.js +112 -0
- 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/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 +28 -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 +162 -0
- package/package.json +3 -1
- package/src/api.ts +77 -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/add-project.ts +159 -0
- package/src/cli/commands/create-customer.ts +185 -0
- package/src/cli/commands/diff.ts +360 -0
- package/src/cli/commands/help.ts +75 -4
- package/src/cli/commands/list-registries.ts +53 -0
- package/src/cli/commands/list-registry-items.ts +149 -0
- 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/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 +35 -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 +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
|
+
}
|
package/src/cli/commands/pull.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
}
|