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,111 @@
1
+ /**
2
+ * ProjectSyncStrategy - Handles synchronization of Projects, Agents, Flows, and Skills
3
+ *
4
+ * This strategy implements ISyncStrategy for the Project resource hierarchy:
5
+ * Project → Agent → Flow → Skill
6
+ *
7
+ * Key responsibilities:
8
+ * - Pull complete project structure from NEWO platform
9
+ * - Push changed skills and new entities to platform
10
+ * - Detect changes using SHA256 hashes
11
+ * - Validate project structure before push
12
+ */
13
+ import type { ISyncStrategy, PullOptions, PullResult, PushResult, ChangeItem, ValidationResult, StatusSummary } from './ISyncStrategy.js';
14
+ import type { CustomerConfig, ILogger } from '../../resources/common/types.js';
15
+ import type { AxiosInstance } from 'axios';
16
+ import type { ProjectMeta, SkillMetadata, FlowMetadata, AgentMetadata, ProjectMetadata } from '../../../types.js';
17
+ /**
18
+ * Project data for local storage
19
+ */
20
+ export interface LocalProjectData {
21
+ projectId: string;
22
+ projectIdn: string;
23
+ metadata: ProjectMetadata;
24
+ agents: LocalAgentData[];
25
+ }
26
+ export interface LocalAgentData {
27
+ id: string;
28
+ idn: string;
29
+ metadata: AgentMetadata;
30
+ flows: LocalFlowData[];
31
+ }
32
+ export interface LocalFlowData {
33
+ id: string;
34
+ idn: string;
35
+ metadata: FlowMetadata;
36
+ skills: LocalSkillData[];
37
+ }
38
+ export interface LocalSkillData {
39
+ id: string;
40
+ idn: string;
41
+ metadata: SkillMetadata;
42
+ scriptPath: string;
43
+ scriptContent: string;
44
+ }
45
+ /**
46
+ * API client factory type
47
+ */
48
+ export type ApiClientFactory = (customer: CustomerConfig, verbose: boolean) => Promise<AxiosInstance>;
49
+ /**
50
+ * ProjectSyncStrategy - Handles project synchronization
51
+ */
52
+ export declare class ProjectSyncStrategy implements ISyncStrategy<ProjectMeta, LocalProjectData> {
53
+ private apiClientFactory;
54
+ private logger;
55
+ readonly resourceType = "projects";
56
+ readonly displayName = "Projects";
57
+ constructor(apiClientFactory: ApiClientFactory, logger: ILogger);
58
+ /**
59
+ * Pull all projects from NEWO platform
60
+ */
61
+ pull(customer: CustomerConfig, options?: PullOptions): Promise<PullResult<LocalProjectData>>;
62
+ /**
63
+ * Pull a single agent and its flows/skills
64
+ */
65
+ private pullAgent;
66
+ /**
67
+ * Pull a single flow and its skills
68
+ */
69
+ private pullFlow;
70
+ /**
71
+ * Pull a single skill
72
+ */
73
+ private pullSkill;
74
+ /**
75
+ * Push changed projects to NEWO platform
76
+ */
77
+ push(customer: CustomerConfig, changes?: ChangeItem<LocalProjectData>[]): Promise<PushResult>;
78
+ /**
79
+ * Push a skill update
80
+ */
81
+ private pushSkillUpdate;
82
+ /**
83
+ * Push a new entity
84
+ */
85
+ private pushNewEntity;
86
+ /**
87
+ * Publish all flows
88
+ */
89
+ private publishAllFlows;
90
+ /**
91
+ * Detect changes in project files
92
+ */
93
+ getChanges(customer: CustomerConfig): Promise<ChangeItem<LocalProjectData>[]>;
94
+ /**
95
+ * Validate project structure
96
+ */
97
+ validate(customer: CustomerConfig, _items: LocalProjectData[]): Promise<ValidationResult>;
98
+ /**
99
+ * Get status summary
100
+ */
101
+ getStatus(customer: CustomerConfig): Promise<StatusSummary>;
102
+ /**
103
+ * Clean up deleted entities
104
+ */
105
+ private cleanupDeletedEntities;
106
+ }
107
+ /**
108
+ * Factory function for creating ProjectSyncStrategy
109
+ */
110
+ export declare function createProjectSyncStrategy(apiClientFactory: ApiClientFactory, logger: ILogger): ProjectSyncStrategy;
111
+ //# sourceMappingURL=ProjectSyncStrategy.d.ts.map
@@ -0,0 +1,523 @@
1
+ /**
2
+ * ProjectSyncStrategy - Handles synchronization of Projects, Agents, Flows, and Skills
3
+ *
4
+ * This strategy implements ISyncStrategy for the Project resource hierarchy:
5
+ * Project → Agent → Flow → Skill
6
+ *
7
+ * Key responsibilities:
8
+ * - Pull complete project structure from NEWO platform
9
+ * - Push changed skills and new entities to platform
10
+ * - Detect changes using SHA256 hashes
11
+ * - Validate project structure before push
12
+ */
13
+ import fs from 'fs-extra';
14
+ import yaml from 'js-yaml';
15
+ import { listProjects, listAgents, listFlowSkills, listFlowEvents, listFlowStates, updateSkill, publishFlow } from '../../../api.js';
16
+ import { ensureState, writeFileSafe, mapPath, projectMetadataPath, agentMetadataPath, flowMetadataPath, skillMetadataPath, skillScriptPath, skillFolderPath, flowsYamlPath, customerProjectsDir, projectDir } from '../../../fsutil.js';
17
+ import { sha256, saveHashes, loadHashes } from '../../../hash.js';
18
+ import { generateFlowsYaml } from '../../../sync/metadata.js';
19
+ import { findSkillScriptFiles, isContentDifferent, getExtensionForRunner, getSingleSkillFile, validateSkillFolder } from '../../../sync/skill-files.js';
20
+ /**
21
+ * ProjectSyncStrategy - Handles project synchronization
22
+ */
23
+ export class ProjectSyncStrategy {
24
+ apiClientFactory;
25
+ logger;
26
+ resourceType = 'projects';
27
+ displayName = 'Projects';
28
+ constructor(apiClientFactory, logger) {
29
+ this.apiClientFactory = apiClientFactory;
30
+ this.logger = logger;
31
+ }
32
+ /**
33
+ * Pull all projects from NEWO platform
34
+ */
35
+ async pull(customer, options = {}) {
36
+ const client = await this.apiClientFactory(customer, options.verbose ?? false);
37
+ const hashes = {};
38
+ const projects = [];
39
+ this.logger.verbose(`📋 Loading project list for customer ${customer.idn}...`);
40
+ await ensureState(customer.idn);
41
+ // Fetch projects
42
+ const apiProjects = options.projectId
43
+ ? [{ id: options.projectId, idn: 'unknown', title: 'Project' }]
44
+ : await listProjects(client);
45
+ if (apiProjects.length === 0) {
46
+ this.logger.info(`No projects found for customer ${customer.idn}`);
47
+ return { items: [], count: 0, hashes: {} };
48
+ }
49
+ // Load existing map for reference
50
+ let existingMap = { projects: {} };
51
+ const mapFile = mapPath(customer.idn);
52
+ if (await fs.pathExists(mapFile)) {
53
+ try {
54
+ const mapData = await fs.readJson(mapFile);
55
+ if (mapData && typeof mapData === 'object' && 'projects' in mapData) {
56
+ existingMap = mapData;
57
+ }
58
+ }
59
+ catch {
60
+ // Ignore errors, start fresh
61
+ }
62
+ }
63
+ // Count total skills for progress
64
+ let totalSkills = 0;
65
+ let processedSkills = 0;
66
+ for (const project of apiProjects) {
67
+ const agents = await listAgents(client, project.id);
68
+ for (const agent of agents) {
69
+ const flows = agent.flows || [];
70
+ for (const flow of flows) {
71
+ const skills = await listFlowSkills(client, flow.id);
72
+ totalSkills += skills.length;
73
+ }
74
+ }
75
+ }
76
+ this.logger.verbose(`📊 Total skills to process: ${totalSkills}`);
77
+ // Process each project
78
+ for (const project of apiProjects) {
79
+ this.logger.verbose(`📁 Processing project: ${project.title} (${project.idn})`);
80
+ const projectMeta = {
81
+ id: project.id,
82
+ idn: project.idn,
83
+ title: project.title,
84
+ description: project.description || '',
85
+ created_at: project.created_at || '',
86
+ updated_at: project.updated_at || ''
87
+ };
88
+ // Save project metadata
89
+ const projectMetaPath = projectMetadataPath(customer.idn, project.idn);
90
+ const projectMetaYaml = yaml.dump(projectMeta, { indent: 2, quotingType: '"', forceQuotes: false });
91
+ await writeFileSafe(projectMetaPath, projectMetaYaml);
92
+ hashes[projectMetaPath] = sha256(projectMetaYaml);
93
+ const localProject = {
94
+ projectId: project.id,
95
+ projectIdn: project.idn,
96
+ metadata: projectMeta,
97
+ agents: []
98
+ };
99
+ const agents = await listAgents(client, project.id);
100
+ this.logger.verbose(` 📋 Found ${agents.length} agents in project ${project.title}`);
101
+ const projectData = {
102
+ projectId: project.id,
103
+ projectIdn: project.idn,
104
+ agents: {}
105
+ };
106
+ // Process each agent
107
+ for (const agent of agents) {
108
+ const localAgent = await this.pullAgent(client, customer, project, agent, hashes, options, () => {
109
+ processedSkills++;
110
+ if (!options.verbose && totalSkills > 0) {
111
+ if (processedSkills % 10 === 0 || processedSkills === totalSkills) {
112
+ this.logger.progress(processedSkills, totalSkills, '📄 Processing skills');
113
+ }
114
+ }
115
+ });
116
+ localProject.agents.push(localAgent);
117
+ // Build project data for map
118
+ projectData.agents[agent.idn] = {
119
+ id: agent.id,
120
+ flows: {}
121
+ };
122
+ for (const flow of localAgent.flows) {
123
+ projectData.agents[agent.idn].flows[flow.idn] = {
124
+ id: flow.id,
125
+ skills: {}
126
+ };
127
+ for (const skill of flow.skills) {
128
+ projectData.agents[agent.idn].flows[flow.idn].skills[skill.idn] = skill.metadata;
129
+ }
130
+ }
131
+ }
132
+ existingMap.projects[project.idn] = projectData;
133
+ projects.push(localProject);
134
+ }
135
+ // Save updated project map
136
+ await writeFileSafe(mapFile, JSON.stringify(existingMap, null, 2));
137
+ // Generate flows.yaml
138
+ const flowsYamlContent = await generateFlowsYaml(existingMap, customer.idn, options.verbose ?? false);
139
+ const flowsYamlFilePath = flowsYamlPath(customer.idn);
140
+ hashes[flowsYamlFilePath] = sha256(flowsYamlContent);
141
+ // Save hashes
142
+ await saveHashes(hashes, customer.idn);
143
+ // Clean up deleted entities if not skipped
144
+ if (!options.skipCleanup) {
145
+ await this.cleanupDeletedEntities(customer.idn, existingMap, options.verbose ?? false);
146
+ }
147
+ return {
148
+ items: projects,
149
+ count: projects.length,
150
+ hashes
151
+ };
152
+ }
153
+ /**
154
+ * Pull a single agent and its flows/skills
155
+ */
156
+ async pullAgent(client, customer, project, agent, hashes, options, onSkillProcessed) {
157
+ this.logger.verbose(` 📁 Processing agent: ${agent.title} (${agent.idn})`);
158
+ const agentMeta = {
159
+ id: agent.id,
160
+ idn: agent.idn,
161
+ title: agent.title || '',
162
+ description: agent.description || ''
163
+ };
164
+ // Save agent metadata
165
+ const agentMetaPath = agentMetadataPath(customer.idn, project.idn, agent.idn);
166
+ const agentMetaYaml = yaml.dump(agentMeta, { indent: 2, quotingType: '"', forceQuotes: false });
167
+ await writeFileSafe(agentMetaPath, agentMetaYaml);
168
+ hashes[agentMetaPath] = sha256(agentMetaYaml);
169
+ const localAgent = {
170
+ id: agent.id,
171
+ idn: agent.idn,
172
+ metadata: agentMeta,
173
+ flows: []
174
+ };
175
+ const flows = agent.flows || [];
176
+ this.logger.verbose(` 📋 Found ${flows.length} flows in agent ${agent.title}`);
177
+ // Process each flow
178
+ for (const flow of flows) {
179
+ const localFlow = await this.pullFlow(client, customer, project, agent, flow, hashes, options, onSkillProcessed);
180
+ localAgent.flows.push(localFlow);
181
+ }
182
+ return localAgent;
183
+ }
184
+ /**
185
+ * Pull a single flow and its skills
186
+ */
187
+ async pullFlow(client, customer, project, agent, flow, hashes, options, onSkillProcessed) {
188
+ this.logger.verbose(` 📁 Processing flow: ${flow.title} (${flow.idn})`);
189
+ // Get flow events and states
190
+ const [events, states] = await Promise.all([
191
+ listFlowEvents(client, flow.id).catch(() => []),
192
+ listFlowStates(client, flow.id).catch(() => [])
193
+ ]);
194
+ const flowMeta = {
195
+ id: flow.id,
196
+ idn: flow.idn,
197
+ title: flow.title,
198
+ description: flow.description || '',
199
+ default_runner_type: flow.default_runner_type,
200
+ default_model: flow.default_model,
201
+ events,
202
+ state_fields: states
203
+ };
204
+ // Save flow metadata
205
+ const flowMetaPath = flowMetadataPath(customer.idn, project.idn, agent.idn, flow.idn);
206
+ const flowMetaYaml = yaml.dump(flowMeta, { indent: 2, quotingType: '"', forceQuotes: false });
207
+ await writeFileSafe(flowMetaPath, flowMetaYaml);
208
+ hashes[flowMetaPath] = sha256(flowMetaYaml);
209
+ const localFlow = {
210
+ id: flow.id,
211
+ idn: flow.idn,
212
+ metadata: flowMeta,
213
+ skills: []
214
+ };
215
+ // Process skills
216
+ const skills = await listFlowSkills(client, flow.id);
217
+ this.logger.verbose(` 📋 Found ${skills.length} skills in flow ${flow.title}`);
218
+ for (const skill of skills) {
219
+ const localSkill = await this.pullSkill(client, customer, project, agent, flow, skill, hashes, options);
220
+ localFlow.skills.push(localSkill);
221
+ onSkillProcessed();
222
+ }
223
+ return localFlow;
224
+ }
225
+ /**
226
+ * Pull a single skill
227
+ */
228
+ async pullSkill(_client, customer, project, agent, flow, skill, hashes, options) {
229
+ this.logger.verbose(` 📄 Processing skill: ${skill.title} (${skill.idn})`);
230
+ const skillMeta = {
231
+ id: skill.id,
232
+ idn: skill.idn,
233
+ title: skill.title,
234
+ runner_type: skill.runner_type,
235
+ model: skill.model,
236
+ parameters: [...skill.parameters],
237
+ path: skill.path
238
+ };
239
+ // Save skill metadata
240
+ const skillMetaPath = skillMetadataPath(customer.idn, project.idn, agent.idn, flow.idn, skill.idn);
241
+ const skillMetaYaml = yaml.dump(skillMeta, { indent: 2, quotingType: '"', forceQuotes: false });
242
+ await writeFileSafe(skillMetaPath, skillMetaYaml);
243
+ hashes[skillMetaPath] = sha256(skillMetaYaml);
244
+ // Handle skill script
245
+ const scriptContent = skill.prompt_script || '';
246
+ const targetScriptPath = skillScriptPath(customer.idn, project.idn, agent.idn, flow.idn, skill.idn, skill.runner_type);
247
+ const folderPath = skillFolderPath(customer.idn, project.idn, agent.idn, flow.idn, skill.idn);
248
+ // Check for existing script files
249
+ const existingFiles = await findSkillScriptFiles(folderPath);
250
+ let shouldWrite = true;
251
+ if (existingFiles.length > 0) {
252
+ const hasContentMatch = existingFiles.some(file => !isContentDifferent(file.content, scriptContent));
253
+ if (hasContentMatch) {
254
+ const matchingFile = existingFiles.find(file => !isContentDifferent(file.content, scriptContent));
255
+ const correctName = `${skill.idn}.${getExtensionForRunner(skill.runner_type)}`;
256
+ if (matchingFile && matchingFile.fileName !== correctName) {
257
+ await fs.remove(matchingFile.filePath);
258
+ this.logger.verbose(` 🔄 Renamed ${matchingFile.fileName} → ${correctName}`);
259
+ }
260
+ else if (matchingFile && matchingFile.fileName === correctName) {
261
+ shouldWrite = false;
262
+ hashes[matchingFile.filePath] = sha256(scriptContent);
263
+ }
264
+ }
265
+ else if (!options.silentOverwrite) {
266
+ // In interactive mode, we'd ask for confirmation
267
+ // For now, just overwrite in strategy (interactive logic stays in CLI)
268
+ for (const file of existingFiles) {
269
+ await fs.remove(file.filePath);
270
+ }
271
+ }
272
+ else {
273
+ for (const file of existingFiles) {
274
+ await fs.remove(file.filePath);
275
+ }
276
+ }
277
+ }
278
+ if (shouldWrite) {
279
+ await writeFileSafe(targetScriptPath, scriptContent);
280
+ hashes[targetScriptPath] = sha256(scriptContent);
281
+ }
282
+ return {
283
+ id: skill.id,
284
+ idn: skill.idn,
285
+ metadata: skillMeta,
286
+ scriptPath: targetScriptPath,
287
+ scriptContent
288
+ };
289
+ }
290
+ /**
291
+ * Push changed projects to NEWO platform
292
+ */
293
+ async push(customer, changes) {
294
+ const result = { created: 0, updated: 0, deleted: 0, errors: [] };
295
+ // If no changes provided, detect them
296
+ if (!changes) {
297
+ changes = await this.getChanges(customer);
298
+ }
299
+ if (changes.length === 0) {
300
+ return result;
301
+ }
302
+ const client = await this.apiClientFactory(customer, false);
303
+ const existingHashes = await loadHashes(customer.idn);
304
+ const newHashes = { ...existingHashes };
305
+ // Load project map
306
+ const mapFile = mapPath(customer.idn);
307
+ if (!(await fs.pathExists(mapFile))) {
308
+ result.errors.push(`No project map found. Run pull first.`);
309
+ return result;
310
+ }
311
+ const mapData = await fs.readJson(mapFile);
312
+ // Process skill changes
313
+ for (const change of changes) {
314
+ try {
315
+ if (change.operation === 'modified') {
316
+ // Update existing skill
317
+ const updateResult = await this.pushSkillUpdate(client, customer, change, mapData, newHashes);
318
+ result.updated += updateResult;
319
+ }
320
+ else if (change.operation === 'created') {
321
+ // Create new entity
322
+ const createResult = await this.pushNewEntity(client, customer, change, mapData, newHashes);
323
+ result.created += createResult;
324
+ }
325
+ }
326
+ catch (error) {
327
+ result.errors.push(`Failed to push ${change.path}: ${error instanceof Error ? error.message : String(error)}`);
328
+ }
329
+ }
330
+ // Save updated hashes
331
+ await saveHashes(newHashes, customer.idn);
332
+ // Publish flows if any changes were made
333
+ if (result.created > 0 || result.updated > 0) {
334
+ await this.publishAllFlows(client, mapData);
335
+ }
336
+ return result;
337
+ }
338
+ /**
339
+ * Push a skill update
340
+ */
341
+ async pushSkillUpdate(client, _customer, change, mapData, newHashes) {
342
+ // Extract skill info from path
343
+ // Path format: newo_customers/{customer}/projects/{project}/{agent}/{flow}/{skill}/{skill}.guidance
344
+ const pathParts = change.path.split('/');
345
+ const skillIdn = pathParts[pathParts.length - 2] || '';
346
+ const flowIdn = pathParts[pathParts.length - 3] || '';
347
+ const agentIdn = pathParts[pathParts.length - 4] || '';
348
+ const projectIdn = pathParts[pathParts.length - 5] || '';
349
+ // Look up skill in map
350
+ const projectData = mapData.projects[projectIdn];
351
+ const agentData = projectData?.agents[agentIdn];
352
+ const flowData = agentData?.flows[flowIdn];
353
+ const skillData = flowData?.skills[skillIdn];
354
+ if (!skillData) {
355
+ throw new Error(`Skill ${skillIdn} not found in project map`);
356
+ }
357
+ // Read script content
358
+ const content = await fs.readFile(change.path, 'utf8');
359
+ // Update skill
360
+ await updateSkill(client, {
361
+ id: skillData.id,
362
+ title: skillData.title,
363
+ idn: skillData.idn,
364
+ prompt_script: content,
365
+ runner_type: skillData.runner_type,
366
+ model: skillData.model,
367
+ parameters: skillData.parameters,
368
+ path: skillData.path
369
+ });
370
+ // Update hash
371
+ newHashes[change.path] = sha256(content);
372
+ this.logger.info(`↑ Pushed: ${skillIdn}`);
373
+ return 1;
374
+ }
375
+ /**
376
+ * Push a new entity
377
+ */
378
+ async pushNewEntity(_client, _customer, _change, _mapData, _newHashes) {
379
+ // Entity creation is handled separately for now
380
+ // This would be expanded for full entity creation support
381
+ return 0;
382
+ }
383
+ /**
384
+ * Publish all flows
385
+ */
386
+ async publishAllFlows(client, mapData) {
387
+ for (const projectData of Object.values(mapData.projects)) {
388
+ for (const agentData of Object.values(projectData.agents)) {
389
+ for (const [flowIdn, flowData] of Object.entries(agentData.flows)) {
390
+ if (flowData.id) {
391
+ try {
392
+ await publishFlow(client, flowData.id, {
393
+ version: '1.0',
394
+ description: 'Published via NEWO CLI',
395
+ type: 'public'
396
+ });
397
+ this.logger.verbose(`📤 Published flow: ${flowIdn}`);
398
+ }
399
+ catch (error) {
400
+ this.logger.warn(`Failed to publish flow ${flowIdn}`);
401
+ }
402
+ }
403
+ }
404
+ }
405
+ }
406
+ }
407
+ /**
408
+ * Detect changes in project files
409
+ */
410
+ async getChanges(customer) {
411
+ const changes = [];
412
+ const mapFile = mapPath(customer.idn);
413
+ if (!(await fs.pathExists(mapFile))) {
414
+ return changes;
415
+ }
416
+ const hashes = await loadHashes(customer.idn);
417
+ const mapData = await fs.readJson(mapFile);
418
+ // Scan for changed skill scripts
419
+ for (const [projectIdn, projectData] of Object.entries(mapData.projects)) {
420
+ for (const [agentIdn, agentData] of Object.entries(projectData.agents)) {
421
+ for (const [flowIdn, flowData] of Object.entries(agentData.flows)) {
422
+ for (const [skillIdn, _skillData] of Object.entries(flowData.skills)) {
423
+ const skillFile = await getSingleSkillFile(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn);
424
+ if (skillFile) {
425
+ const currentHash = sha256(skillFile.content);
426
+ const storedHash = hashes[skillFile.filePath];
427
+ if (storedHash !== currentHash) {
428
+ changes.push({
429
+ item: {}, // Simplified for now
430
+ operation: 'modified',
431
+ path: skillFile.filePath
432
+ });
433
+ }
434
+ }
435
+ }
436
+ }
437
+ }
438
+ }
439
+ return changes;
440
+ }
441
+ /**
442
+ * Validate project structure
443
+ */
444
+ async validate(customer, _items) {
445
+ const errors = [];
446
+ const mapFile = mapPath(customer.idn);
447
+ if (!(await fs.pathExists(mapFile))) {
448
+ errors.push({
449
+ field: 'projectMap',
450
+ message: 'No project map found. Run pull first.'
451
+ });
452
+ return { valid: false, errors };
453
+ }
454
+ const mapData = await fs.readJson(mapFile);
455
+ // Validate skill folders
456
+ for (const [projectIdn, projectData] of Object.entries(mapData.projects)) {
457
+ for (const [agentIdn, agentData] of Object.entries(projectData.agents)) {
458
+ for (const [flowIdn, flowData] of Object.entries(agentData.flows)) {
459
+ for (const skillIdn of Object.keys(flowData.skills)) {
460
+ const validation = await validateSkillFolder(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn);
461
+ if (!validation.isValid) {
462
+ for (const error of validation.errors) {
463
+ errors.push({
464
+ field: `skill.${skillIdn}`,
465
+ message: error,
466
+ path: `${projectIdn}/${agentIdn}/${flowIdn}/${skillIdn}`
467
+ });
468
+ }
469
+ }
470
+ }
471
+ }
472
+ }
473
+ }
474
+ return { valid: errors.length === 0, errors };
475
+ }
476
+ /**
477
+ * Get status summary
478
+ */
479
+ async getStatus(customer) {
480
+ const changes = await this.getChanges(customer);
481
+ return {
482
+ resourceType: this.resourceType,
483
+ displayName: this.displayName,
484
+ changedCount: changes.length,
485
+ changes: changes.map(c => ({
486
+ path: c.path,
487
+ operation: c.operation
488
+ }))
489
+ };
490
+ }
491
+ /**
492
+ * Clean up deleted entities
493
+ */
494
+ async cleanupDeletedEntities(customerIdn, projectMap, verbose) {
495
+ const projectsPath = customerProjectsDir(customerIdn);
496
+ if (!(await fs.pathExists(projectsPath))) {
497
+ return;
498
+ }
499
+ const localProjects = await fs.readdir(projectsPath);
500
+ const deletedPaths = [];
501
+ for (const localProjectIdn of localProjects) {
502
+ const localProjectPath = projectDir(customerIdn, localProjectIdn);
503
+ const stat = await fs.stat(localProjectPath).catch(() => null);
504
+ if (!stat || !stat.isDirectory())
505
+ continue;
506
+ if (localProjectIdn === 'flows.yaml')
507
+ continue;
508
+ if (!projectMap.projects[localProjectIdn]) {
509
+ deletedPaths.push(localProjectPath);
510
+ }
511
+ }
512
+ if (deletedPaths.length > 0 && verbose) {
513
+ this.logger.info(`Found ${deletedPaths.length} deleted entities`);
514
+ }
515
+ }
516
+ }
517
+ /**
518
+ * Factory function for creating ProjectSyncStrategy
519
+ */
520
+ export function createProjectSyncStrategy(apiClientFactory, logger) {
521
+ return new ProjectSyncStrategy(apiClientFactory, logger);
522
+ }
523
+ //# sourceMappingURL=ProjectSyncStrategy.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Sync Strategies Module Exports
3
+ *
4
+ * This module exports all sync strategies that implement ISyncStrategy.
5
+ * Each strategy handles synchronization for a specific resource type.
6
+ */
7
+ export * from './ISyncStrategy.js';
8
+ export { ProjectSyncStrategy, createProjectSyncStrategy, type LocalProjectData, type LocalFlowData, type LocalSkillData } from './ProjectSyncStrategy.js';
9
+ export { AttributeSyncStrategy, createAttributeSyncStrategy, type LocalAttributeData } from './AttributeSyncStrategy.js';
10
+ export { IntegrationSyncStrategy, createIntegrationSyncStrategy, type LocalIntegrationData } from './IntegrationSyncStrategy.js';
11
+ export { AkbSyncStrategy, createAkbSyncStrategy, type LocalAkbData } from './AkbSyncStrategy.js';
12
+ export { ConversationSyncStrategy, createConversationSyncStrategy, type LocalConversationData } from './ConversationSyncStrategy.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Sync Strategies Module Exports
3
+ *
4
+ * This module exports all sync strategies that implement ISyncStrategy.
5
+ * Each strategy handles synchronization for a specific resource type.
6
+ */
7
+ // Core interface
8
+ export * from './ISyncStrategy.js';
9
+ // Project strategy (projects, agents, flows, skills)
10
+ export { ProjectSyncStrategy, createProjectSyncStrategy } from './ProjectSyncStrategy.js';
11
+ // Attribute strategy (customer and project attributes)
12
+ export { AttributeSyncStrategy, createAttributeSyncStrategy } from './AttributeSyncStrategy.js';
13
+ // Integration strategy (integrations, connectors, webhooks)
14
+ export { IntegrationSyncStrategy, createIntegrationSyncStrategy } from './IntegrationSyncStrategy.js';
15
+ // AKB strategy (knowledge base articles)
16
+ export { AkbSyncStrategy, createAkbSyncStrategy } from './AkbSyncStrategy.js';
17
+ // Conversation strategy (conversation history - pull only)
18
+ export { ConversationSyncStrategy, createConversationSyncStrategy } from './ConversationSyncStrategy.js';
19
+ //# sourceMappingURL=index.js.map