newo 3.2.0 ā 3.3.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 +85 -1
- package/README.md +44 -0
- package/dist/api.d.ts +30 -0
- package/dist/api.js +17 -0
- package/dist/cli/commands/create-webhooks.d.ts +3 -0
- package/dist/cli/commands/create-webhooks.js +83 -0
- package/dist/cli/commands/help.js +5 -0
- package/dist/cli/commands/migrate-account.d.ts +3 -0
- package/dist/cli/commands/migrate-account.js +84 -0
- package/dist/cli/commands/verify-migration.d.ts +3 -0
- package/dist/cli/commands/verify-migration.js +68 -0
- package/dist/cli.js +13 -0
- package/dist/sync/migrate.d.ts +32 -0
- package/dist/sync/migrate.js +590 -0
- package/dist/sync.d.ts +1 -0
- package/dist/sync.js +1 -0
- package/dist/types.d.ts +15 -1
- package/package.json +7 -3
- package/src/api.ts +56 -0
- package/src/cli/commands/create-webhooks.ts +100 -0
- package/src/cli/commands/help.ts +5 -0
- package/src/cli/commands/migrate-account.ts +104 -0
- package/src/cli/commands/verify-migration.ts +88 -0
- package/src/cli.ts +16 -0
- package/src/sync/migrate.ts +746 -0
- package/src/sync.ts +1 -0
- package/src/types.ts +15 -1
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Account migration operations
|
|
3
|
+
* Migrates complete account from source to destination
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
listProjects, listAgents, createProject, createAgent, createFlow, createSkill,
|
|
8
|
+
createFlowEvent, createFlowState,
|
|
9
|
+
getCustomerAttributes, createCustomerAttribute, updateCustomerAttribute,
|
|
10
|
+
getProjectAttributes, createProjectAttribute, updateProjectAttribute,
|
|
11
|
+
searchPersonas, getAkbTopics, createPersona, importAkbArticle,
|
|
12
|
+
listIntegrations, listConnectors, createConnector,
|
|
13
|
+
listFlowSkills, updateSkill
|
|
14
|
+
} from '../api.js';
|
|
15
|
+
import { pullAll } from './projects.js';
|
|
16
|
+
import { pullIntegrations } from './integrations.js';
|
|
17
|
+
import { customerDir, customerProjectsDir } from '../fsutil.js';
|
|
18
|
+
import fs from 'fs-extra';
|
|
19
|
+
import yaml from 'js-yaml';
|
|
20
|
+
import path from 'path';
|
|
21
|
+
import type { AxiosInstance } from 'axios';
|
|
22
|
+
import type {
|
|
23
|
+
CustomerConfig,
|
|
24
|
+
CreateFlowEventRequest, AkbImportArticle
|
|
25
|
+
} from '../types.js';
|
|
26
|
+
|
|
27
|
+
interface MigrationOptions {
|
|
28
|
+
sourceCustomer: CustomerConfig;
|
|
29
|
+
destCustomer: CustomerConfig;
|
|
30
|
+
sourceClient: AxiosInstance;
|
|
31
|
+
destClient: AxiosInstance;
|
|
32
|
+
verbose: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface MigrationResult {
|
|
36
|
+
success: boolean;
|
|
37
|
+
projectsCreated: number;
|
|
38
|
+
agentsCreated: number;
|
|
39
|
+
flowsCreated: number;
|
|
40
|
+
skillsCreated: number;
|
|
41
|
+
attributesMigrated: number;
|
|
42
|
+
personasCreated: number;
|
|
43
|
+
articlesImported: number;
|
|
44
|
+
connectorsCreated: number;
|
|
45
|
+
webhooksCreated: number;
|
|
46
|
+
errors: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Migrate complete account from source to destination
|
|
51
|
+
*/
|
|
52
|
+
export async function migrateAccount(options: MigrationOptions): Promise<MigrationResult> {
|
|
53
|
+
const { sourceCustomer, destCustomer, sourceClient, destClient, verbose } = options;
|
|
54
|
+
|
|
55
|
+
const result: MigrationResult = {
|
|
56
|
+
success: false,
|
|
57
|
+
projectsCreated: 0,
|
|
58
|
+
agentsCreated: 0,
|
|
59
|
+
flowsCreated: 0,
|
|
60
|
+
skillsCreated: 0,
|
|
61
|
+
attributesMigrated: 0,
|
|
62
|
+
personasCreated: 0,
|
|
63
|
+
articlesImported: 0,
|
|
64
|
+
connectorsCreated: 0,
|
|
65
|
+
webhooksCreated: 0,
|
|
66
|
+
errors: []
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
console.log('\nš Starting account migration...');
|
|
71
|
+
console.log(`Source: ${sourceCustomer.idn}`);
|
|
72
|
+
console.log(`Destination: ${destCustomer.idn}\n`);
|
|
73
|
+
|
|
74
|
+
// Step 1: Pull source data
|
|
75
|
+
console.log('š„ Step 1: Pulling source data...');
|
|
76
|
+
await pullAll(sourceClient, sourceCustomer, null, verbose, true);
|
|
77
|
+
await pullIntegrations(sourceClient, customerDir(sourceCustomer.idn), verbose);
|
|
78
|
+
console.log(' ā
Source data pulled\n');
|
|
79
|
+
|
|
80
|
+
// Step 2: Create project structure
|
|
81
|
+
console.log('šļø Step 2: Creating project structure...');
|
|
82
|
+
const projectCounts = await migrateProjectStructure(sourceClient, destClient, sourceCustomer, destCustomer, verbose);
|
|
83
|
+
result.projectsCreated = projectCounts.projects;
|
|
84
|
+
result.agentsCreated = projectCounts.agents;
|
|
85
|
+
result.flowsCreated = projectCounts.flows;
|
|
86
|
+
result.skillsCreated = projectCounts.skills;
|
|
87
|
+
console.log(` ā
Created: ${result.projectsCreated} projects, ${result.agentsCreated} agents, ${result.flowsCreated} flows, ${result.skillsCreated} skills\n`);
|
|
88
|
+
|
|
89
|
+
// Step 3: Migrate attributes
|
|
90
|
+
console.log('š Step 3: Migrating attributes...');
|
|
91
|
+
result.attributesMigrated = await migrateAttributes(sourceClient, destClient, sourceCustomer, destCustomer, verbose);
|
|
92
|
+
console.log(` ā
Migrated: ${result.attributesMigrated} attributes\n`);
|
|
93
|
+
|
|
94
|
+
// Step 4: Migrate AKB
|
|
95
|
+
console.log('š Step 4: Migrating AKB...');
|
|
96
|
+
const akbCounts = await migrateAKB(sourceClient, destClient, verbose);
|
|
97
|
+
result.personasCreated = akbCounts.personas;
|
|
98
|
+
result.articlesImported = akbCounts.articles;
|
|
99
|
+
console.log(` ā
Migrated: ${result.personasCreated} personas, ${result.articlesImported} articles\n`);
|
|
100
|
+
|
|
101
|
+
// Step 5: Migrate integrations
|
|
102
|
+
console.log('š Step 5: Migrating integrations...');
|
|
103
|
+
result.connectorsCreated = await migrateIntegrationConnectors(sourceClient, destClient, verbose);
|
|
104
|
+
console.log(` ā
Created: ${result.connectorsCreated} connectors\n`);
|
|
105
|
+
|
|
106
|
+
// Step 6: Copy files
|
|
107
|
+
console.log('š Step 6: Copying files...');
|
|
108
|
+
await copyAccountFiles(sourceCustomer.idn, destCustomer.idn);
|
|
109
|
+
console.log(' ā
Files copied\n');
|
|
110
|
+
|
|
111
|
+
// Step 7: Build map from API
|
|
112
|
+
console.log('š Step 7: Building destination mappings...');
|
|
113
|
+
await buildMapFromAPI(destClient, destCustomer, verbose);
|
|
114
|
+
console.log(' ā
Mappings built\n');
|
|
115
|
+
|
|
116
|
+
// Step 8: Push skill content
|
|
117
|
+
console.log('š¤ Step 8: Pushing skill content...');
|
|
118
|
+
const skillsPushed = await pushSkillContent(destClient, destCustomer, verbose);
|
|
119
|
+
console.log(` ā
Pushed: ${skillsPushed} skills\n`);
|
|
120
|
+
|
|
121
|
+
// Step 9: Create webhooks
|
|
122
|
+
console.log('š” Step 9: Creating webhooks...');
|
|
123
|
+
result.webhooksCreated = await createWebhooksFromYAML(destClient, destCustomer, verbose);
|
|
124
|
+
console.log(` ā
Created: ${result.webhooksCreated} webhooks\n`);
|
|
125
|
+
|
|
126
|
+
// Step 10: Verify
|
|
127
|
+
console.log('ā
Step 10: Verifying migration...');
|
|
128
|
+
await verifyMigration(sourceClient, destClient, sourceCustomer, destCustomer);
|
|
129
|
+
|
|
130
|
+
result.success = true;
|
|
131
|
+
console.log('\nš MIGRATION COMPLETED SUCCESSFULLY!\n');
|
|
132
|
+
|
|
133
|
+
} catch (error) {
|
|
134
|
+
result.success = false;
|
|
135
|
+
result.errors.push(error instanceof Error ? error.message : String(error));
|
|
136
|
+
console.error(`\nā Migration failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function migrateProjectStructure(
|
|
143
|
+
sourceClient: AxiosInstance,
|
|
144
|
+
destClient: AxiosInstance,
|
|
145
|
+
sourceCustomer: CustomerConfig,
|
|
146
|
+
// @ts-ignore - Parameter kept for API consistency
|
|
147
|
+
destCustomer: CustomerConfig,
|
|
148
|
+
// @ts-ignore - Parameter kept for future use
|
|
149
|
+
verbose: boolean
|
|
150
|
+
): Promise<{ projects: number; agents: number; flows: number; skills: number }> {
|
|
151
|
+
const sourceProjects = await listProjects(sourceClient);
|
|
152
|
+
const destProjects = await listProjects(destClient);
|
|
153
|
+
const destProjectMap = new Map(destProjects.map(p => [p.idn, p]));
|
|
154
|
+
|
|
155
|
+
let projectsCreated = 0;
|
|
156
|
+
let agentsCreated = 0;
|
|
157
|
+
let flowsCreated = 0;
|
|
158
|
+
let skillsCreated = 0;
|
|
159
|
+
|
|
160
|
+
for (const sourceProj of sourceProjects) {
|
|
161
|
+
let projectId: string;
|
|
162
|
+
|
|
163
|
+
// Create or get existing project
|
|
164
|
+
const existingProj = destProjectMap.get(sourceProj.idn);
|
|
165
|
+
if (existingProj) {
|
|
166
|
+
projectId = existingProj.id;
|
|
167
|
+
if (verbose) console.log(` ā Project ${sourceProj.idn} already exists`);
|
|
168
|
+
} else {
|
|
169
|
+
const projResponse = await createProject(destClient, {
|
|
170
|
+
idn: sourceProj.idn,
|
|
171
|
+
title: sourceProj.title,
|
|
172
|
+
description: sourceProj.description || '',
|
|
173
|
+
is_auto_update_enabled: sourceProj.is_auto_update_enabled || false,
|
|
174
|
+
registry_idn: sourceProj.registry_idn || 'production'
|
|
175
|
+
});
|
|
176
|
+
projectId = projResponse.id;
|
|
177
|
+
projectsCreated++;
|
|
178
|
+
if (verbose) console.log(` ā
Created project: ${sourceProj.idn}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Create agents
|
|
182
|
+
const sourceAgents = await listAgents(sourceClient, sourceProj.id);
|
|
183
|
+
const destAgents = await listAgents(destClient, projectId);
|
|
184
|
+
const destAgentMap = new Map(destAgents.map(a => [a.idn, a]));
|
|
185
|
+
|
|
186
|
+
for (const sourceAgent of sourceAgents) {
|
|
187
|
+
let agentId: string;
|
|
188
|
+
|
|
189
|
+
const existingAgent = destAgentMap.get(sourceAgent.idn);
|
|
190
|
+
if (existingAgent) {
|
|
191
|
+
agentId = existingAgent.id;
|
|
192
|
+
} else {
|
|
193
|
+
const agentResponse = await createAgent(destClient, projectId, {
|
|
194
|
+
idn: sourceAgent.idn,
|
|
195
|
+
title: sourceAgent.title || sourceAgent.idn,
|
|
196
|
+
description: sourceAgent.description || null
|
|
197
|
+
});
|
|
198
|
+
agentId = agentResponse.id;
|
|
199
|
+
agentsCreated++;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Create flows
|
|
203
|
+
const sourceFlows = sourceAgent.flows || [];
|
|
204
|
+
const destAgentData = await listAgents(destClient, projectId);
|
|
205
|
+
const destAgentWithFlows = destAgentData.find(a => a.id === agentId);
|
|
206
|
+
const destFlowMap = new Map((destAgentWithFlows?.flows || []).map(f => [f.idn, f]));
|
|
207
|
+
|
|
208
|
+
for (const sourceFlow of sourceFlows) {
|
|
209
|
+
let flowId: string;
|
|
210
|
+
|
|
211
|
+
const existingFlow = destFlowMap.get(sourceFlow.idn);
|
|
212
|
+
if (existingFlow) {
|
|
213
|
+
flowId = existingFlow.id;
|
|
214
|
+
} else {
|
|
215
|
+
const flowResponse = await createFlow(destClient, agentId, {
|
|
216
|
+
idn: sourceFlow.idn,
|
|
217
|
+
title: sourceFlow.title
|
|
218
|
+
});
|
|
219
|
+
flowId = flowResponse.id;
|
|
220
|
+
flowsCreated++;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Read flow metadata for events and states
|
|
224
|
+
const flowMetaPath = path.join(
|
|
225
|
+
customerProjectsDir(sourceCustomer.idn),
|
|
226
|
+
sourceProj.idn,
|
|
227
|
+
sourceAgent.idn,
|
|
228
|
+
sourceFlow.idn,
|
|
229
|
+
'metadata.yaml'
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
if (await fs.pathExists(flowMetaPath)) {
|
|
233
|
+
const flowMeta = yaml.load(await fs.readFile(flowMetaPath, 'utf8')) as any;
|
|
234
|
+
|
|
235
|
+
// Create skills
|
|
236
|
+
const destSkills = await listFlowSkills(destClient, flowId);
|
|
237
|
+
const destSkillMap = new Map(destSkills.map(s => [s.idn, s]));
|
|
238
|
+
|
|
239
|
+
for (const sourceSkill of flowMeta.skills || []) {
|
|
240
|
+
if (destSkillMap.has(sourceSkill.idn)) continue;
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
await createSkill(destClient, flowId, {
|
|
244
|
+
idn: sourceSkill.idn,
|
|
245
|
+
title: sourceSkill.title,
|
|
246
|
+
runner_type: sourceSkill.runner_type,
|
|
247
|
+
model: sourceSkill.model,
|
|
248
|
+
prompt_script: ''
|
|
249
|
+
});
|
|
250
|
+
skillsCreated++;
|
|
251
|
+
} catch (error: any) {
|
|
252
|
+
if (verbose && error.response?.status !== 409) {
|
|
253
|
+
console.error(` ā ļø Failed to create skill ${sourceSkill.idn}: ${error.message}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Create events with full metadata
|
|
259
|
+
for (const event of flowMeta.events || []) {
|
|
260
|
+
try {
|
|
261
|
+
const eventRequest: CreateFlowEventRequest = {
|
|
262
|
+
idn: event.idn,
|
|
263
|
+
description: event.description || event.idn,
|
|
264
|
+
skill_selector: event.skill_selector || 'first',
|
|
265
|
+
interrupt_mode: event.interrupt_mode || 'allow',
|
|
266
|
+
integration_idn: event.integration_idn || '',
|
|
267
|
+
connector_idn: event.connector_idn || ''
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
if (event.skill_idn) {
|
|
271
|
+
(eventRequest as any).skill_idn = event.skill_idn;
|
|
272
|
+
}
|
|
273
|
+
if (event.state_idn) {
|
|
274
|
+
(eventRequest as any).state_idn = event.state_idn;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
await createFlowEvent(destClient, flowId, eventRequest);
|
|
278
|
+
} catch (error: any) {
|
|
279
|
+
if (verbose && error.response?.status !== 409 && error.response?.status !== 422) {
|
|
280
|
+
console.error(` ā ļø Failed to create event ${event.idn}: ${error.message}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Create states
|
|
286
|
+
for (const state of flowMeta.state_fields || []) {
|
|
287
|
+
try {
|
|
288
|
+
await createFlowState(destClient, flowId, {
|
|
289
|
+
title: state.title || state.idn,
|
|
290
|
+
idn: state.idn,
|
|
291
|
+
scope: state.scope || 'flow'
|
|
292
|
+
});
|
|
293
|
+
} catch (error: any) {
|
|
294
|
+
if (verbose && error.response?.status !== 409) {
|
|
295
|
+
console.error(` ā ļø Failed to create state ${state.idn}: ${error.message}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return { projects: projectsCreated, agents: agentsCreated, flows: flowsCreated, skills: skillsCreated };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function migrateAttributes(
|
|
308
|
+
sourceClient: AxiosInstance,
|
|
309
|
+
destClient: AxiosInstance,
|
|
310
|
+
// @ts-ignore - Parameter kept for API consistency
|
|
311
|
+
sourceCustomer: CustomerConfig,
|
|
312
|
+
// @ts-ignore - Parameter kept for API consistency
|
|
313
|
+
destCustomer: CustomerConfig,
|
|
314
|
+
// @ts-ignore - Parameter kept for future use
|
|
315
|
+
verbose: boolean
|
|
316
|
+
): Promise<number> {
|
|
317
|
+
let count = 0;
|
|
318
|
+
|
|
319
|
+
// Customer attributes
|
|
320
|
+
const sourceAttrs = await getCustomerAttributes(sourceClient, true);
|
|
321
|
+
const destAttrs = await getCustomerAttributes(destClient, true);
|
|
322
|
+
const destAttrMap = new Map(destAttrs.attributes.map(a => [a.idn, a]));
|
|
323
|
+
|
|
324
|
+
for (const sourceAttr of sourceAttrs.attributes) {
|
|
325
|
+
const destAttr = destAttrMap.get(sourceAttr.idn);
|
|
326
|
+
|
|
327
|
+
if (!destAttr) {
|
|
328
|
+
await createCustomerAttribute(destClient, {
|
|
329
|
+
idn: sourceAttr.idn,
|
|
330
|
+
title: sourceAttr.title,
|
|
331
|
+
description: sourceAttr.description || '',
|
|
332
|
+
value: typeof sourceAttr.value === 'object' ? JSON.stringify(sourceAttr.value) : sourceAttr.value,
|
|
333
|
+
value_type: sourceAttr.value_type,
|
|
334
|
+
group: sourceAttr.group || '',
|
|
335
|
+
is_hidden: sourceAttr.is_hidden || false,
|
|
336
|
+
possible_values: sourceAttr.possible_values || []
|
|
337
|
+
});
|
|
338
|
+
count++;
|
|
339
|
+
} else if (JSON.stringify(destAttr.value) !== JSON.stringify(sourceAttr.value)) {
|
|
340
|
+
if (destAttr.id) {
|
|
341
|
+
await updateCustomerAttribute(destClient, {
|
|
342
|
+
...sourceAttr,
|
|
343
|
+
id: destAttr.id
|
|
344
|
+
});
|
|
345
|
+
count++;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Project attributes
|
|
351
|
+
const sourceProjects = await listProjects(sourceClient);
|
|
352
|
+
const destProjects = await listProjects(destClient);
|
|
353
|
+
const destProjectMap = new Map(destProjects.map(p => [p.idn, p]));
|
|
354
|
+
|
|
355
|
+
for (const sourceProj of sourceProjects) {
|
|
356
|
+
const destProj = destProjectMap.get(sourceProj.idn);
|
|
357
|
+
if (!destProj) continue;
|
|
358
|
+
|
|
359
|
+
const sourceProjAttrs = await getProjectAttributes(sourceClient, sourceProj.id, true);
|
|
360
|
+
const destProjAttrs = await getProjectAttributes(destClient, destProj.id, true);
|
|
361
|
+
const destProjAttrMap = new Map(destProjAttrs.attributes.map(a => [a.idn, a]));
|
|
362
|
+
|
|
363
|
+
for (const sourceAttr of sourceProjAttrs.attributes) {
|
|
364
|
+
const destAttr = destProjAttrMap.get(sourceAttr.idn);
|
|
365
|
+
|
|
366
|
+
if (!destAttr) {
|
|
367
|
+
await createProjectAttribute(destClient, destProj.id, {
|
|
368
|
+
idn: sourceAttr.idn,
|
|
369
|
+
title: sourceAttr.title,
|
|
370
|
+
description: sourceAttr.description || '',
|
|
371
|
+
value: typeof sourceAttr.value === 'object' ? JSON.stringify(sourceAttr.value) : sourceAttr.value,
|
|
372
|
+
value_type: sourceAttr.value_type,
|
|
373
|
+
group: sourceAttr.group || '',
|
|
374
|
+
is_hidden: sourceAttr.is_hidden || false,
|
|
375
|
+
possible_values: sourceAttr.possible_values || []
|
|
376
|
+
});
|
|
377
|
+
count++;
|
|
378
|
+
} else if (JSON.stringify(destAttr.value) !== JSON.stringify(sourceAttr.value)) {
|
|
379
|
+
await updateProjectAttribute(destClient, destProj.id, {
|
|
380
|
+
...destAttr,
|
|
381
|
+
value: sourceAttr.value,
|
|
382
|
+
title: sourceAttr.title,
|
|
383
|
+
description: sourceAttr.description
|
|
384
|
+
});
|
|
385
|
+
count++;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return count;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function migrateAKB(
|
|
394
|
+
sourceClient: AxiosInstance,
|
|
395
|
+
destClient: AxiosInstance,
|
|
396
|
+
// @ts-ignore - Parameter kept for future use
|
|
397
|
+
verbose: boolean
|
|
398
|
+
): Promise<{ personas: number; articles: number }> {
|
|
399
|
+
const sourcePersonas = await searchPersonas(sourceClient, true);
|
|
400
|
+
const destPersonas = await searchPersonas(destClient, true);
|
|
401
|
+
const destPersonaMap = new Map(destPersonas.items.map(p => [p.name, p]));
|
|
402
|
+
|
|
403
|
+
let personasCreated = 0;
|
|
404
|
+
let articlesImported = 0;
|
|
405
|
+
|
|
406
|
+
for (const sourcePersona of sourcePersonas.items) {
|
|
407
|
+
if (destPersonaMap.has(sourcePersona.name)) continue;
|
|
408
|
+
|
|
409
|
+
const newPersona = await createPersona(destClient, {
|
|
410
|
+
name: sourcePersona.name,
|
|
411
|
+
title: sourcePersona.title,
|
|
412
|
+
description: sourcePersona.description || ''
|
|
413
|
+
});
|
|
414
|
+
personasCreated++;
|
|
415
|
+
|
|
416
|
+
// Import articles
|
|
417
|
+
let page = 1;
|
|
418
|
+
let hasMore = true;
|
|
419
|
+
|
|
420
|
+
while (hasMore) {
|
|
421
|
+
const topics = await getAkbTopics(sourceClient, sourcePersona.id, page, 50);
|
|
422
|
+
|
|
423
|
+
if (topics.items.length === 0) break;
|
|
424
|
+
|
|
425
|
+
for (const topicItem of topics.items) {
|
|
426
|
+
const articleData: AkbImportArticle = {
|
|
427
|
+
persona_id: newPersona.id,
|
|
428
|
+
topic_name: topicItem.topic.topic_name,
|
|
429
|
+
source: topicItem.topic.source || '',
|
|
430
|
+
topic_summary: topicItem.topic.topic_summary || '',
|
|
431
|
+
topic_facts: topicItem.topic.topic_facts || [],
|
|
432
|
+
labels: topicItem.topic.labels || [],
|
|
433
|
+
confidence: topicItem.topic.confidence || 1.0
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
await importAkbArticle(destClient, articleData);
|
|
437
|
+
articlesImported++;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
page++;
|
|
441
|
+
hasMore = topics.items.length >= 50;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return { personas: personasCreated, articles: articlesImported };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function migrateIntegrationConnectors(
|
|
449
|
+
sourceClient: AxiosInstance,
|
|
450
|
+
destClient: AxiosInstance,
|
|
451
|
+
// @ts-ignore - Parameter kept for future use
|
|
452
|
+
verbose: boolean
|
|
453
|
+
): Promise<number> {
|
|
454
|
+
const sourceIntegrations = await listIntegrations(sourceClient);
|
|
455
|
+
const destIntegrations = await listIntegrations(destClient);
|
|
456
|
+
const destIntMap = new Map(destIntegrations.map(i => [i.idn, i]));
|
|
457
|
+
|
|
458
|
+
let connectorsCreated = 0;
|
|
459
|
+
|
|
460
|
+
for (const sourceInt of sourceIntegrations) {
|
|
461
|
+
const destInt = destIntMap.get(sourceInt.idn);
|
|
462
|
+
if (!destInt) continue;
|
|
463
|
+
|
|
464
|
+
const sourceConnectors = await listConnectors(sourceClient, sourceInt.id);
|
|
465
|
+
const destConnectors = await listConnectors(destClient, destInt.id);
|
|
466
|
+
const destConnMap = new Map(destConnectors.map(c => [c.connector_idn, c]));
|
|
467
|
+
|
|
468
|
+
for (const sourceConn of sourceConnectors) {
|
|
469
|
+
if (destConnMap.has(sourceConn.connector_idn)) continue;
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
await createConnector(destClient, destInt.id, {
|
|
473
|
+
title: sourceConn.title,
|
|
474
|
+
connector_idn: sourceConn.connector_idn,
|
|
475
|
+
integration_idn: sourceInt.idn,
|
|
476
|
+
settings: sourceConn.settings
|
|
477
|
+
});
|
|
478
|
+
connectorsCreated++;
|
|
479
|
+
} catch (error: any) {
|
|
480
|
+
if (verbose && error.response?.status !== 409) {
|
|
481
|
+
console.error(` ā ļø Failed to create connector ${sourceConn.connector_idn}: ${error.message}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
return connectorsCreated;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
async function copyAccountFiles(sourceIdn: string, destIdn: string): Promise<void> {
|
|
491
|
+
const sourceDir = customerDir(sourceIdn);
|
|
492
|
+
const destDir = customerDir(destIdn);
|
|
493
|
+
|
|
494
|
+
await fs.ensureDir(destDir);
|
|
495
|
+
|
|
496
|
+
// Copy projects
|
|
497
|
+
const sourceProjects = path.join(sourceDir, 'projects');
|
|
498
|
+
const destProjects = path.join(destDir, 'projects');
|
|
499
|
+
if (await fs.pathExists(sourceProjects)) {
|
|
500
|
+
await fs.copy(sourceProjects, destProjects);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Copy integrations
|
|
504
|
+
const sourceIntegrations = path.join(sourceDir, 'integrations');
|
|
505
|
+
const destIntegrations = path.join(destDir, 'integrations');
|
|
506
|
+
if (await fs.pathExists(sourceIntegrations)) {
|
|
507
|
+
await fs.copy(sourceIntegrations, destIntegrations);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Copy AKB
|
|
511
|
+
const sourceAkb = path.join(sourceDir, 'akb');
|
|
512
|
+
const destAkb = path.join(destDir, 'akb');
|
|
513
|
+
if (await fs.pathExists(sourceAkb)) {
|
|
514
|
+
await fs.copy(sourceAkb, destAkb);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Copy attributes
|
|
518
|
+
const sourceAttrs = path.join(sourceDir, 'attributes.yaml');
|
|
519
|
+
const destAttrs = path.join(destDir, 'attributes.yaml');
|
|
520
|
+
if (await fs.pathExists(sourceAttrs)) {
|
|
521
|
+
await fs.copy(sourceAttrs, destAttrs);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async function buildMapFromAPI(
|
|
526
|
+
destClient: AxiosInstance,
|
|
527
|
+
destCustomer: CustomerConfig,
|
|
528
|
+
// @ts-ignore - Parameter kept for future use
|
|
529
|
+
verbose: boolean
|
|
530
|
+
): Promise<void> {
|
|
531
|
+
const newoDir = path.join('.newo', destCustomer.idn);
|
|
532
|
+
await fs.ensureDir(newoDir);
|
|
533
|
+
|
|
534
|
+
const projects = await listProjects(destClient);
|
|
535
|
+
const projectMap: any = { projects: {} };
|
|
536
|
+
|
|
537
|
+
for (const project of projects.filter(p => p.idn !== 'test')) {
|
|
538
|
+
const agents = await listAgents(destClient, project.id);
|
|
539
|
+
const projectData: any = {
|
|
540
|
+
projectId: project.id,
|
|
541
|
+
projectIdn: project.idn,
|
|
542
|
+
agents: {}
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
for (const agent of agents) {
|
|
546
|
+
projectData.agents[agent.idn] = {
|
|
547
|
+
id: agent.id,
|
|
548
|
+
flows: {}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
for (const flow of agent.flows || []) {
|
|
552
|
+
const skills = await listFlowSkills(destClient, flow.id);
|
|
553
|
+
const skillMap: any = {};
|
|
554
|
+
|
|
555
|
+
for (const skill of skills) {
|
|
556
|
+
skillMap[skill.idn] = {
|
|
557
|
+
id: skill.id,
|
|
558
|
+
idn: skill.idn,
|
|
559
|
+
title: skill.title,
|
|
560
|
+
runner_type: skill.runner_type,
|
|
561
|
+
model: skill.model,
|
|
562
|
+
parameters: skill.parameters,
|
|
563
|
+
path: skill.path
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
projectData.agents[agent.idn].flows[flow.idn] = {
|
|
568
|
+
id: flow.id,
|
|
569
|
+
skills: skillMap
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
projectMap.projects[project.idn] = projectData;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
await fs.writeJson(path.join(newoDir, 'map.json'), projectMap, { spaces: 2 });
|
|
578
|
+
await fs.writeJson(path.join(newoDir, 'hashes.json'), {}, { spaces: 2 });
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async function pushSkillContent(
|
|
582
|
+
destClient: AxiosInstance,
|
|
583
|
+
destCustomer: CustomerConfig,
|
|
584
|
+
// @ts-ignore - Parameter kept for future use
|
|
585
|
+
verbose: boolean
|
|
586
|
+
): Promise<number> {
|
|
587
|
+
const mapPath = path.join('.newo', destCustomer.idn, 'map.json');
|
|
588
|
+
const projectMap = await fs.readJson(mapPath) as any;
|
|
589
|
+
const destDir = customerDir(destCustomer.idn);
|
|
590
|
+
|
|
591
|
+
let pushedCount = 0;
|
|
592
|
+
|
|
593
|
+
for (const [projectIdn, projectData] of Object.entries(projectMap.projects || {})) {
|
|
594
|
+
const typedProjectData = projectData as any;
|
|
595
|
+
|
|
596
|
+
for (const [agentIdn, agentData] of Object.entries(typedProjectData.agents || {})) {
|
|
597
|
+
const typedAgentData = agentData as any;
|
|
598
|
+
|
|
599
|
+
for (const [flowIdn, flowData] of Object.entries(typedAgentData.flows || {})) {
|
|
600
|
+
const typedFlowData = flowData as any;
|
|
601
|
+
|
|
602
|
+
for (const [skillIdn, skillData] of Object.entries(typedFlowData.skills || {})) {
|
|
603
|
+
const typedSkillData = skillData as any;
|
|
604
|
+
const extension = typedSkillData.runner_type === 'nsl' ? 'jinja' : 'guidance';
|
|
605
|
+
|
|
606
|
+
const skillFilePath = path.join(
|
|
607
|
+
destDir,
|
|
608
|
+
'projects',
|
|
609
|
+
projectIdn,
|
|
610
|
+
agentIdn,
|
|
611
|
+
flowIdn,
|
|
612
|
+
skillIdn,
|
|
613
|
+
`${skillIdn}.${extension}`
|
|
614
|
+
);
|
|
615
|
+
|
|
616
|
+
if (await fs.pathExists(skillFilePath)) {
|
|
617
|
+
const content = await fs.readFile(skillFilePath, 'utf8');
|
|
618
|
+
|
|
619
|
+
if (content.trim().length > 0) {
|
|
620
|
+
try {
|
|
621
|
+
await updateSkill(destClient, {
|
|
622
|
+
...typedSkillData,
|
|
623
|
+
prompt_script: content
|
|
624
|
+
});
|
|
625
|
+
pushedCount++;
|
|
626
|
+
|
|
627
|
+
if (pushedCount % 100 === 0 && verbose) {
|
|
628
|
+
console.log(` Progress: ${pushedCount} skills pushed...`);
|
|
629
|
+
}
|
|
630
|
+
} catch (error: any) {
|
|
631
|
+
if (verbose) {
|
|
632
|
+
console.error(` ā ļø Failed to push ${skillIdn}: ${error.message}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return pushedCount;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
async function createWebhooksFromYAML(
|
|
646
|
+
destClient: AxiosInstance,
|
|
647
|
+
destCustomer: CustomerConfig,
|
|
648
|
+
// @ts-ignore - Parameter kept for future use
|
|
649
|
+
verbose: boolean
|
|
650
|
+
): Promise<number> {
|
|
651
|
+
const destDir = customerDir(destCustomer.idn);
|
|
652
|
+
let webhooksCreated = 0;
|
|
653
|
+
|
|
654
|
+
// Outgoing webhooks
|
|
655
|
+
const outgoingFile = path.join(destDir, 'integrations/api/connectors/webhook/webhooks/outgoing.yaml');
|
|
656
|
+
if (await fs.pathExists(outgoingFile)) {
|
|
657
|
+
const outgoingData = yaml.load(await fs.readFile(outgoingFile, 'utf8')) as any;
|
|
658
|
+
const webhooks = outgoingData.webhooks || [];
|
|
659
|
+
|
|
660
|
+
for (const webhook of webhooks) {
|
|
661
|
+
try {
|
|
662
|
+
await destClient.post('/api/v1/webhooks', {
|
|
663
|
+
idn: webhook.idn,
|
|
664
|
+
description: webhook.description || '',
|
|
665
|
+
connector_idn: webhook.connector_idn,
|
|
666
|
+
url: webhook.url,
|
|
667
|
+
command_idns: webhook.command_idns || []
|
|
668
|
+
});
|
|
669
|
+
webhooksCreated++;
|
|
670
|
+
} catch (error: any) {
|
|
671
|
+
if (error.response?.status !== 409 && verbose) {
|
|
672
|
+
console.error(` ā ļø Failed to create webhook ${webhook.idn}: ${error.message}`);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Incoming webhooks
|
|
679
|
+
const incomingFile = path.join(destDir, 'integrations/api/connectors/webhook/webhooks/incoming.yaml');
|
|
680
|
+
if (await fs.pathExists(incomingFile)) {
|
|
681
|
+
const incomingData = yaml.load(await fs.readFile(incomingFile, 'utf8')) as any;
|
|
682
|
+
const webhooks = incomingData.webhooks || [];
|
|
683
|
+
|
|
684
|
+
for (const webhook of webhooks) {
|
|
685
|
+
try {
|
|
686
|
+
await destClient.post('/api/v1/webhooks/incoming', {
|
|
687
|
+
idn: webhook.idn,
|
|
688
|
+
description: webhook.description || '',
|
|
689
|
+
connector_idn: webhook.connector_idn,
|
|
690
|
+
event_idns: webhook.event_idns || [],
|
|
691
|
+
allowed_ips: webhook.allowed_ips || []
|
|
692
|
+
});
|
|
693
|
+
webhooksCreated++;
|
|
694
|
+
} catch (error: any) {
|
|
695
|
+
if (error.response?.status !== 409 && verbose) {
|
|
696
|
+
console.error(` ā ļø Failed to create webhook ${webhook.idn}: ${error.message}`);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return webhooksCreated;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function verifyMigration(
|
|
706
|
+
sourceClient: AxiosInstance,
|
|
707
|
+
destClient: AxiosInstance,
|
|
708
|
+
// @ts-ignore - Parameter kept for API consistency
|
|
709
|
+
sourceCustomer: CustomerConfig,
|
|
710
|
+
// @ts-ignore - Parameter kept for API consistency
|
|
711
|
+
destCustomer: CustomerConfig
|
|
712
|
+
): Promise<void> {
|
|
713
|
+
const sourceProjects = await listProjects(sourceClient);
|
|
714
|
+
const destProjects = await listProjects(destClient);
|
|
715
|
+
|
|
716
|
+
let srcSkills = 0;
|
|
717
|
+
let dstSkills = 0;
|
|
718
|
+
|
|
719
|
+
for (const proj of sourceProjects) {
|
|
720
|
+
const agents = await listAgents(sourceClient, proj.id);
|
|
721
|
+
for (const agent of agents) {
|
|
722
|
+
for (const flow of agent.flows || []) {
|
|
723
|
+
const skills = await listFlowSkills(sourceClient, flow.id);
|
|
724
|
+
srcSkills += skills.length;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
for (const proj of destProjects.filter(p => p.idn !== 'test')) {
|
|
730
|
+
const agents = await listAgents(destClient, proj.id);
|
|
731
|
+
for (const agent of agents) {
|
|
732
|
+
for (const flow of agent.flows || []) {
|
|
733
|
+
const skills = await listFlowSkills(destClient, flow.id);
|
|
734
|
+
dstSkills += skills.length;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
console.log(` Skills: ${srcSkills} source ā ${dstSkills} destination ${srcSkills === dstSkills ? 'ā
' : 'ā'}`);
|
|
740
|
+
|
|
741
|
+
if (srcSkills !== dstSkills) {
|
|
742
|
+
throw new Error(`Skill count mismatch: ${srcSkills} source vs ${dstSkills} destination`);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
console.log(' ā
Verification passed');
|
|
746
|
+
}
|