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,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
|