newo 1.9.1 → 2.0.0

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 (66) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +68 -20
  3. package/dist/cli/commands/conversations.d.ts +3 -0
  4. package/dist/cli/commands/conversations.js +38 -0
  5. package/dist/cli/commands/help.d.ts +5 -0
  6. package/dist/cli/commands/help.js +50 -0
  7. package/dist/cli/commands/import-akb.d.ts +3 -0
  8. package/dist/cli/commands/import-akb.js +62 -0
  9. package/dist/cli/commands/list-customers.d.ts +3 -0
  10. package/dist/cli/commands/list-customers.js +13 -0
  11. package/dist/cli/commands/meta.d.ts +3 -0
  12. package/dist/cli/commands/meta.js +19 -0
  13. package/dist/cli/commands/pull-attributes.d.ts +3 -0
  14. package/dist/cli/commands/pull-attributes.js +16 -0
  15. package/dist/cli/commands/pull.d.ts +3 -0
  16. package/dist/cli/commands/pull.js +34 -0
  17. package/dist/cli/commands/push.d.ts +3 -0
  18. package/dist/cli/commands/push.js +39 -0
  19. package/dist/cli/commands/status.d.ts +3 -0
  20. package/dist/cli/commands/status.js +22 -0
  21. package/dist/cli/customer-selection.d.ts +23 -0
  22. package/dist/cli/customer-selection.js +110 -0
  23. package/dist/cli/errors.d.ts +9 -0
  24. package/dist/cli/errors.js +111 -0
  25. package/dist/cli.js +66 -463
  26. package/dist/fsutil.js +1 -1
  27. package/dist/sync/attributes.d.ts +7 -0
  28. package/dist/sync/attributes.js +90 -0
  29. package/dist/sync/conversations.d.ts +7 -0
  30. package/dist/sync/conversations.js +218 -0
  31. package/dist/sync/metadata.d.ts +8 -0
  32. package/dist/sync/metadata.js +124 -0
  33. package/dist/sync/projects.d.ts +13 -0
  34. package/dist/sync/projects.js +283 -0
  35. package/dist/sync/push.d.ts +7 -0
  36. package/dist/sync/push.js +171 -0
  37. package/dist/sync/skill-files.d.ts +42 -0
  38. package/dist/sync/skill-files.js +121 -0
  39. package/dist/sync/status.d.ts +6 -0
  40. package/dist/sync/status.js +247 -0
  41. package/dist/sync.d.ts +10 -8
  42. package/dist/sync.js +12 -1197
  43. package/dist/types.d.ts +0 -1
  44. package/package.json +2 -2
  45. package/src/cli/commands/conversations.ts +47 -0
  46. package/src/cli/commands/help.ts +50 -0
  47. package/src/cli/commands/import-akb.ts +71 -0
  48. package/src/cli/commands/list-customers.ts +14 -0
  49. package/src/cli/commands/meta.ts +26 -0
  50. package/src/cli/commands/pull-attributes.ts +23 -0
  51. package/src/cli/commands/pull.ts +43 -0
  52. package/src/cli/commands/push.ts +47 -0
  53. package/src/cli/commands/status.ts +30 -0
  54. package/src/cli/customer-selection.ts +135 -0
  55. package/src/cli/errors.ts +111 -0
  56. package/src/cli.ts +77 -471
  57. package/src/fsutil.ts +1 -1
  58. package/src/sync/attributes.ts +110 -0
  59. package/src/sync/conversations.ts +257 -0
  60. package/src/sync/metadata.ts +153 -0
  61. package/src/sync/projects.ts +359 -0
  62. package/src/sync/push.ts +200 -0
  63. package/src/sync/skill-files.ts +176 -0
  64. package/src/sync/status.ts +277 -0
  65. package/src/sync.ts +14 -1389
  66. package/src/types.ts +0 -1
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Project synchronization operations
3
+ */
4
+ import { listProjects, listAgents, listFlowSkills, listFlowEvents, listFlowStates } from '../api.js';
5
+ import { ensureState, writeFileSafe, mapPath, projectMetadataPath, agentMetadataPath, flowMetadataPath, skillMetadataPath, skillScriptPath, skillFolderPath, flowsYamlPath, customerAttributesPath } from '../fsutil.js';
6
+ import { findSkillScriptFiles, isContentDifferent, askForOverwrite, getExtensionForRunner } from './skill-files.js';
7
+ import fs from 'fs-extra';
8
+ import { sha256, saveHashes } from '../hash.js';
9
+ import yaml from 'js-yaml';
10
+ import { generateFlowsYaml } from './metadata.js';
11
+ import { saveCustomerAttributes } from './attributes.js';
12
+ // Type guards for project map formats
13
+ export function isProjectMap(x) {
14
+ return typeof x === 'object' && x !== null && 'projects' in x;
15
+ }
16
+ export function isLegacyProjectMap(x) {
17
+ return typeof x === 'object' && x !== null && 'projectId' in x && 'agents' in x;
18
+ }
19
+ /**
20
+ * Pull a single project and all its data
21
+ */
22
+ export async function pullSingleProject(client, customer, projectId, verbose = false, silentOverwrite = false) {
23
+ if (verbose)
24
+ console.log(`šŸ“‹ Loading project list for customer ${customer.idn}...`);
25
+ const projects = projectId ?
26
+ [{ id: projectId, idn: 'unknown', title: 'Project' }] :
27
+ await listProjects(client);
28
+ if (projects.length === 0) {
29
+ console.log(`No projects found for customer ${customer.idn}`);
30
+ return;
31
+ }
32
+ await ensureState(customer.idn);
33
+ // Load existing mappings if they exist
34
+ let existingMap = { projects: {} };
35
+ const mapFile = mapPath(customer.idn);
36
+ if (await fs.pathExists(mapFile)) {
37
+ const mapData = await fs.readJson(mapFile);
38
+ if (isProjectMap(mapData)) {
39
+ existingMap = mapData;
40
+ }
41
+ else if (isLegacyProjectMap(mapData)) {
42
+ // Convert legacy format to new format
43
+ existingMap = {
44
+ projects: {
45
+ [mapData.projectIdn || '']: mapData
46
+ }
47
+ };
48
+ }
49
+ }
50
+ const newHashes = {};
51
+ // Progress tracking
52
+ let totalSkills = 0;
53
+ let processedSkills = 0;
54
+ // Count total skills for progress tracking
55
+ for (const project of projects) {
56
+ const agents = await listAgents(client, project.id);
57
+ for (const agent of agents) {
58
+ const flows = agent.flows || [];
59
+ for (const flow of flows) {
60
+ const skills = await listFlowSkills(client, flow.id);
61
+ totalSkills += skills.length;
62
+ }
63
+ }
64
+ }
65
+ if (verbose)
66
+ console.log(`šŸ“Š Total skills to process: ${totalSkills}`);
67
+ for (const project of projects) {
68
+ if (verbose)
69
+ console.log(`šŸ“ Processing project: ${project.title} (${project.idn})`);
70
+ // Create project metadata
71
+ const projectMeta = {
72
+ id: project.id,
73
+ idn: project.idn,
74
+ title: project.title,
75
+ description: project.description || '',
76
+ created_at: project.created_at || '',
77
+ updated_at: project.updated_at || ''
78
+ };
79
+ // Save project metadata
80
+ const projectMetaPath = projectMetadataPath(customer.idn, project.idn);
81
+ const projectMetaYaml = yaml.dump(projectMeta, { indent: 2, quotingType: '"', forceQuotes: false });
82
+ await writeFileSafe(projectMetaPath, projectMetaYaml);
83
+ newHashes[projectMetaPath] = sha256(projectMetaYaml);
84
+ const agents = await listAgents(client, project.id);
85
+ if (verbose)
86
+ console.log(` šŸ“‹ Found ${agents.length} agents in project ${project.title}`);
87
+ const projectData = {
88
+ projectId: project.id,
89
+ projectIdn: project.idn,
90
+ agents: {}
91
+ };
92
+ for (const agent of agents) {
93
+ if (verbose)
94
+ console.log(` šŸ“ Processing agent: ${agent.title} (${agent.idn})`);
95
+ // Create agent metadata
96
+ const agentMeta = {
97
+ id: agent.id,
98
+ idn: agent.idn,
99
+ title: agent.title || '',
100
+ description: agent.description || ''
101
+ };
102
+ // Save agent metadata
103
+ const agentMetaPath = agentMetadataPath(customer.idn, project.idn, agent.idn);
104
+ const agentMetaYaml = yaml.dump(agentMeta, { indent: 2, quotingType: '"', forceQuotes: false });
105
+ await writeFileSafe(agentMetaPath, agentMetaYaml);
106
+ newHashes[agentMetaPath] = sha256(agentMetaYaml);
107
+ projectData.agents[agent.idn] = {
108
+ id: agent.id,
109
+ flows: {}
110
+ };
111
+ const flows = agent.flows || [];
112
+ if (verbose && flows.length > 0) {
113
+ console.log(` šŸ“‹ Found ${flows.length} flows in agent ${agent.title}`);
114
+ }
115
+ for (const flow of flows) {
116
+ if (verbose)
117
+ console.log(` šŸ“ Processing flow: ${flow.title} (${flow.idn})`);
118
+ // Get flow events and states for metadata
119
+ const [events, states] = await Promise.all([
120
+ listFlowEvents(client, flow.id).catch(() => []),
121
+ listFlowStates(client, flow.id).catch(() => [])
122
+ ]);
123
+ // Create flow metadata
124
+ const flowMeta = {
125
+ id: flow.id,
126
+ idn: flow.idn,
127
+ title: flow.title,
128
+ description: flow.description || '',
129
+ default_runner_type: flow.default_runner_type,
130
+ default_model: flow.default_model,
131
+ events,
132
+ state_fields: states
133
+ };
134
+ // Save flow metadata
135
+ const flowMetaPath = flowMetadataPath(customer.idn, project.idn, agent.idn, flow.idn);
136
+ const flowMetaYaml = yaml.dump(flowMeta, { indent: 2, quotingType: '"', forceQuotes: false });
137
+ await writeFileSafe(flowMetaPath, flowMetaYaml);
138
+ newHashes[flowMetaPath] = sha256(flowMetaYaml);
139
+ projectData.agents[agent.idn].flows[flow.idn] = {
140
+ id: flow.id,
141
+ skills: {}
142
+ };
143
+ const skills = await listFlowSkills(client, flow.id);
144
+ if (verbose)
145
+ console.log(` šŸ“‹ Found ${skills.length} skills in flow ${flow.title}`);
146
+ for (const skill of skills) {
147
+ processedSkills++;
148
+ const progress = `[${processedSkills}/${totalSkills}]`;
149
+ if (verbose) {
150
+ console.log(` šŸ“„ ${progress} Processing skill: ${skill.title} (${skill.idn})`);
151
+ }
152
+ else {
153
+ // Show progress for non-verbose mode
154
+ if (processedSkills % 10 === 0 || processedSkills === totalSkills) {
155
+ process.stdout.write(`\ršŸ“„ Processing skills: ${processedSkills}/${totalSkills} (${Math.round(processedSkills / totalSkills * 100)}%)`);
156
+ }
157
+ }
158
+ // Create skill metadata
159
+ const skillMeta = {
160
+ id: skill.id,
161
+ idn: skill.idn,
162
+ title: skill.title,
163
+ runner_type: skill.runner_type,
164
+ model: skill.model,
165
+ parameters: [...skill.parameters],
166
+ path: skill.path
167
+ };
168
+ // Save skill metadata
169
+ const skillMetaPath = skillMetadataPath(customer.idn, project.idn, agent.idn, flow.idn, skill.idn);
170
+ const skillMetaYaml = yaml.dump(skillMeta, { indent: 2, quotingType: '"', forceQuotes: false });
171
+ await writeFileSafe(skillMetaPath, skillMetaYaml);
172
+ newHashes[skillMetaPath] = sha256(skillMetaYaml);
173
+ // Handle skill script with IDN-based naming and overwrite detection
174
+ const scriptContent = skill.prompt_script || '';
175
+ const targetScriptPath = skillScriptPath(customer.idn, project.idn, agent.idn, flow.idn, skill.idn, skill.runner_type);
176
+ const folderPath = skillFolderPath(customer.idn, project.idn, agent.idn, flow.idn, skill.idn);
177
+ // Check for existing script files in the skill folder
178
+ const existingFiles = await findSkillScriptFiles(folderPath);
179
+ let shouldWrite = true;
180
+ let hasContentMatch = false;
181
+ if (existingFiles.length > 0) {
182
+ // Check if any existing file has the same content
183
+ hasContentMatch = existingFiles.some(file => !isContentDifferent(file.content, scriptContent));
184
+ if (hasContentMatch) {
185
+ // Content is the same - remove old files and write with correct IDN name
186
+ const matchingFile = existingFiles.find(file => !isContentDifferent(file.content, scriptContent));
187
+ const correctName = `${skill.idn}.${getExtensionForRunner(skill.runner_type)}`;
188
+ if (matchingFile && matchingFile.fileName !== correctName) {
189
+ // Remove old file and write with correct IDN-based name
190
+ await fs.remove(matchingFile.filePath);
191
+ if (verbose)
192
+ console.log(` šŸ”„ Renamed ${matchingFile.fileName} → ${correctName}`);
193
+ }
194
+ else if (matchingFile && matchingFile.fileName === correctName) {
195
+ // Already has correct name and content
196
+ shouldWrite = false;
197
+ newHashes[matchingFile.filePath] = sha256(scriptContent);
198
+ if (verbose)
199
+ console.log(` āœ“ Content unchanged for ${skill.idn}, keeping existing file`);
200
+ }
201
+ }
202
+ else if (!silentOverwrite) {
203
+ // Content is different, ask for overwrite
204
+ const existingFile = existingFiles[0];
205
+ const shouldOverwrite = await askForOverwrite(skill.idn, existingFile.fileName, `${skill.idn}.${getExtensionForRunner(skill.runner_type)}`);
206
+ if (!shouldOverwrite) {
207
+ shouldWrite = false;
208
+ if (verbose)
209
+ console.log(` āš ļø Skipped overwrite for ${skill.idn}`);
210
+ }
211
+ else {
212
+ // Remove existing files before writing new one
213
+ for (const file of existingFiles) {
214
+ await fs.remove(file.filePath);
215
+ if (verbose)
216
+ console.log(` šŸ—‘ļø Removed ${file.fileName}`);
217
+ }
218
+ }
219
+ }
220
+ else {
221
+ // Silent overwrite mode - remove existing files
222
+ for (const file of existingFiles) {
223
+ await fs.remove(file.filePath);
224
+ if (verbose)
225
+ console.log(` šŸ”„ Silent overwrite: removed ${file.fileName}`);
226
+ }
227
+ }
228
+ }
229
+ if (shouldWrite) {
230
+ await writeFileSafe(targetScriptPath, scriptContent);
231
+ newHashes[targetScriptPath] = sha256(scriptContent);
232
+ const fileName = `${skill.idn}.${getExtensionForRunner(skill.runner_type)}`;
233
+ if (verbose)
234
+ console.log(` āœ“ Saved ${fileName}`);
235
+ }
236
+ projectData.agents[agent.idn].flows[flow.idn].skills[skill.idn] = skillMeta;
237
+ }
238
+ }
239
+ }
240
+ // Store project data in map
241
+ existingMap.projects[project.idn] = projectData;
242
+ }
243
+ // Clear progress line for non-verbose mode
244
+ if (!verbose && totalSkills > 0) {
245
+ console.log(`\nāœ… Processed ${totalSkills} skills`);
246
+ }
247
+ // Save updated project map
248
+ await writeFileSafe(mapFile, JSON.stringify(existingMap, null, 2));
249
+ // Pull customer attributes as part of the project pull
250
+ try {
251
+ if (verbose)
252
+ console.log(`šŸ” Fetching customer attributes for ${customer.idn}...`);
253
+ const attributesContent = await saveCustomerAttributes(client, customer, verbose);
254
+ // Add attributes.yaml hash to the hash store
255
+ const attributesPath = customerAttributesPath(customer.idn);
256
+ newHashes[attributesPath] = sha256(attributesContent);
257
+ if (verbose)
258
+ console.log(`āœ… Customer attributes saved to newo_customers/${customer.idn}/attributes.yaml`);
259
+ }
260
+ catch (error) {
261
+ console.warn(`āš ļø Failed to fetch customer attributes for ${customer.idn}: ${error instanceof Error ? error.message : String(error)}`);
262
+ if (verbose)
263
+ console.warn('You can manually pull attributes using: newo pull-attributes');
264
+ }
265
+ // Generate flows.yaml and get its content for hashing
266
+ const flowsYamlContent = await generateFlowsYaml(existingMap, customer.idn, verbose);
267
+ // Add flows.yaml hash to the hash store
268
+ const flowsYamlFilePath = flowsYamlPath(customer.idn);
269
+ newHashes[flowsYamlFilePath] = sha256(flowsYamlContent);
270
+ // Save hashes (now including flows.yaml and attributes.yaml)
271
+ await saveHashes(newHashes, customer.idn);
272
+ }
273
+ /**
274
+ * Pull all projects for a customer
275
+ */
276
+ export async function pullAll(client, customer, projectId = null, verbose = false, silentOverwrite = false) {
277
+ if (verbose)
278
+ console.log(`šŸ”„ Starting pull operation for customer ${customer.idn}...`);
279
+ await pullSingleProject(client, customer, projectId, verbose, silentOverwrite);
280
+ if (verbose)
281
+ console.log(`āœ… Pull completed for customer ${customer.idn}`);
282
+ }
283
+ //# sourceMappingURL=projects.js.map
@@ -0,0 +1,7 @@
1
+ import type { AxiosInstance } from 'axios';
2
+ import type { CustomerConfig } from '../types.js';
3
+ /**
4
+ * Push changed files to NEWO platform
5
+ */
6
+ export declare function pushChanged(client: AxiosInstance, customer: CustomerConfig, verbose?: boolean): Promise<void>;
7
+ //# sourceMappingURL=push.d.ts.map
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Push operations for changed files
3
+ */
4
+ import { updateSkill } from '../api.js';
5
+ import { ensureState, mapPath, skillMetadataPath } from '../fsutil.js';
6
+ import { validateSkillFolder, getSingleSkillFile } from './skill-files.js';
7
+ import fs from 'fs-extra';
8
+ import { sha256, loadHashes, saveHashes } from '../hash.js';
9
+ import yaml from 'js-yaml';
10
+ import { generateFlowsYaml } from './metadata.js';
11
+ import { isProjectMap, isLegacyProjectMap } from './projects.js';
12
+ import { flowsYamlPath } from '../fsutil.js';
13
+ /**
14
+ * Push changed files to NEWO platform
15
+ */
16
+ export async function pushChanged(client, customer, verbose = false) {
17
+ await ensureState(customer.idn);
18
+ if (!(await fs.pathExists(mapPath(customer.idn)))) {
19
+ console.log(`No map for customer ${customer.idn}. Run \`newo pull --customer ${customer.idn}\` first.`);
20
+ return;
21
+ }
22
+ if (verbose)
23
+ console.log(`šŸ“‹ Loading project mapping and hashes for customer ${customer.idn}...`);
24
+ const idMapData = await fs.readJson(mapPath(customer.idn));
25
+ const hashes = await loadHashes(customer.idn);
26
+ const newHashes = { ...hashes };
27
+ let pushed = 0;
28
+ let scanned = 0;
29
+ let metadataChanged = false;
30
+ // Handle both old single-project format and new multi-project format with type guards
31
+ const projects = isProjectMap(idMapData) && idMapData.projects
32
+ ? idMapData.projects
33
+ : isLegacyProjectMap(idMapData)
34
+ ? { '': idMapData }
35
+ : (() => { throw new Error('Invalid project map format'); })();
36
+ for (const [projectIdn, projectData] of Object.entries(projects)) {
37
+ if (verbose && projectIdn)
38
+ console.log(`šŸ“ Checking project: ${projectIdn}`);
39
+ for (const [agentIdn, agentObj] of Object.entries(projectData.agents)) {
40
+ if (verbose)
41
+ console.log(` šŸ“ Checking agent: ${agentIdn}`);
42
+ for (const [flowIdn, flowObj] of Object.entries(agentObj.flows)) {
43
+ if (verbose)
44
+ console.log(` šŸ“ Checking flow: ${flowIdn}`);
45
+ for (const [skillIdn, skillMeta] of Object.entries(flowObj.skills)) {
46
+ scanned++;
47
+ // Validate skill folder has exactly one script file
48
+ const validation = await validateSkillFolder(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn);
49
+ if (!validation.isValid) {
50
+ // Show warnings and errors
51
+ validation.errors.forEach(error => {
52
+ console.error(`āŒ ${error}`);
53
+ });
54
+ validation.warnings.forEach(warning => {
55
+ console.warn(`āš ļø ${warning}`);
56
+ });
57
+ if (validation.files.length > 1) {
58
+ console.warn(`āš ļø Skipping push for skill ${skillIdn} - multiple script files found:`);
59
+ validation.files.forEach(file => {
60
+ console.warn(` • ${file.fileName}`);
61
+ });
62
+ console.warn(` Please keep only one script file and try again.`);
63
+ }
64
+ continue;
65
+ }
66
+ // Get the single valid script file
67
+ const skillFile = await getSingleSkillFile(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn);
68
+ if (!skillFile) {
69
+ if (verbose)
70
+ console.log(` āŒ No valid script file found for: ${skillIdn}`);
71
+ continue;
72
+ }
73
+ const content = skillFile.content;
74
+ const currentPath = skillFile.filePath;
75
+ const h = sha256(content);
76
+ const oldHash = hashes[currentPath];
77
+ if (oldHash !== h) {
78
+ if (verbose)
79
+ console.log(`šŸ”„ Script changed, updating: ${skillIdn} (${skillFile.fileName})`);
80
+ try {
81
+ // Create skill object for update
82
+ const skillObject = {
83
+ id: skillMeta.id,
84
+ title: skillMeta.title,
85
+ idn: skillMeta.idn,
86
+ prompt_script: content,
87
+ runner_type: skillMeta.runner_type,
88
+ model: skillMeta.model,
89
+ parameters: skillMeta.parameters,
90
+ path: skillMeta.path || undefined
91
+ };
92
+ await updateSkill(client, skillObject);
93
+ console.log(`↑ Pushed: ${skillIdn} (${skillMeta.title}) from ${skillFile.fileName}`);
94
+ newHashes[currentPath] = h;
95
+ pushed++;
96
+ }
97
+ catch (error) {
98
+ console.error(`āŒ Failed to push ${skillIdn}: ${error instanceof Error ? error.message : String(error)}`);
99
+ }
100
+ }
101
+ else if (verbose) {
102
+ console.log(` āœ“ No changes: ${skillIdn} (${skillFile.fileName})`);
103
+ }
104
+ }
105
+ // Check for metadata-only changes and push them separately
106
+ for (const [skillIdn] of Object.entries(flowObj.skills)) {
107
+ const metadataPath = projectIdn ?
108
+ skillMetadataPath(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn) :
109
+ skillMetadataPath(customer.idn, '', agentIdn, flowIdn, skillIdn);
110
+ if (await fs.pathExists(metadataPath)) {
111
+ const metadataContent = await fs.readFile(metadataPath, 'utf8');
112
+ const h = sha256(metadataContent);
113
+ const oldHash = hashes[metadataPath];
114
+ if (oldHash !== h) {
115
+ if (verbose)
116
+ console.log(`šŸ”„ Metadata-only change detected for ${skillIdn}, updating skill...`);
117
+ try {
118
+ // Load updated metadata
119
+ const updatedMetadata = yaml.load(metadataContent);
120
+ // Get current script content using file validation
121
+ const skillFile = await getSingleSkillFile(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn);
122
+ let scriptContent = '';
123
+ if (skillFile) {
124
+ scriptContent = skillFile.content;
125
+ }
126
+ else {
127
+ console.warn(`āš ļø No valid script file found for metadata update: ${skillIdn}`);
128
+ continue;
129
+ }
130
+ // Create skill object with updated metadata
131
+ const skillObject = {
132
+ id: updatedMetadata.id,
133
+ title: updatedMetadata.title,
134
+ idn: updatedMetadata.idn,
135
+ prompt_script: scriptContent,
136
+ runner_type: updatedMetadata.runner_type,
137
+ model: updatedMetadata.model,
138
+ parameters: updatedMetadata.parameters,
139
+ path: updatedMetadata.path || undefined
140
+ };
141
+ await updateSkill(client, skillObject);
142
+ console.log(`↑ Pushed metadata update for skill: ${skillIdn} (${updatedMetadata.title})`);
143
+ newHashes[metadataPath] = h;
144
+ pushed++;
145
+ metadataChanged = true;
146
+ }
147
+ catch (error) {
148
+ console.error(`āŒ Failed to push metadata for ${skillIdn}: ${error instanceof Error ? error.message : String(error)}`);
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ }
156
+ if (verbose)
157
+ console.log(`šŸ”„ Scanned ${scanned} files, found ${pushed} changes`);
158
+ // Regenerate flows.yaml if metadata was changed
159
+ if (metadataChanged) {
160
+ if (verbose)
161
+ console.log(`šŸ”„ Regenerating flows.yaml due to metadata changes...`);
162
+ const flowsYamlContent = await generateFlowsYaml({ projects }, customer.idn, verbose);
163
+ // Update hash for flows.yaml
164
+ const flowsYamlFilePath = flowsYamlPath(customer.idn);
165
+ newHashes[flowsYamlFilePath] = sha256(flowsYamlContent);
166
+ }
167
+ // Save updated hashes
168
+ await saveHashes(newHashes, customer.idn);
169
+ console.log(pushed ? `${pushed} file(s) pushed.` : 'No changes to push.');
170
+ }
171
+ //# sourceMappingURL=push.js.map
@@ -0,0 +1,42 @@
1
+ import type { RunnerType } from '../types.js';
2
+ export interface SkillFile {
3
+ filePath: string;
4
+ fileName: string;
5
+ extension: string;
6
+ content: string;
7
+ }
8
+ export interface SkillFileValidation {
9
+ isValid: boolean;
10
+ files: SkillFile[];
11
+ warnings: string[];
12
+ errors: string[];
13
+ }
14
+ /**
15
+ * Get the correct file extension for a runner type
16
+ */
17
+ export declare function getExtensionForRunner(runnerType: RunnerType): string;
18
+ /**
19
+ * Generate IDN-based script file path
20
+ */
21
+ export declare function getIdnBasedScriptPath(customerIdn: string, projectIdn: string, agentIdn: string, flowIdn: string, skillIdn: string, runnerType: RunnerType): string;
22
+ /**
23
+ * Find all script files in a skill folder
24
+ */
25
+ export declare function findSkillScriptFiles(skillFolderPath: string): Promise<SkillFile[]>;
26
+ /**
27
+ * Validate skill folder has exactly one script file
28
+ */
29
+ export declare function validateSkillFolder(customerIdn: string, projectIdn: string, agentIdn: string, flowIdn: string, skillIdn: string): Promise<SkillFileValidation>;
30
+ /**
31
+ * Get the single skill script file (if valid)
32
+ */
33
+ export declare function getSingleSkillFile(customerIdn: string, projectIdn: string, agentIdn: string, flowIdn: string, skillIdn: string): Promise<SkillFile | null>;
34
+ /**
35
+ * Check if skill script content is different from target content
36
+ */
37
+ export declare function isContentDifferent(existingContent: string, newContent: string): boolean;
38
+ /**
39
+ * Interactive overwrite confirmation
40
+ */
41
+ export declare function askForOverwrite(skillIdn: string, existingFile: string, newFile: string): Promise<boolean>;
42
+ //# sourceMappingURL=skill-files.d.ts.map
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Skill file management utilities
3
+ */
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
6
+ import { sha256 } from '../hash.js';
7
+ import { skillFolderPath } from '../fsutil.js';
8
+ /**
9
+ * Get the correct file extension for a runner type
10
+ */
11
+ export function getExtensionForRunner(runnerType) {
12
+ switch (runnerType) {
13
+ case 'guidance':
14
+ return 'guidance';
15
+ case 'nsl':
16
+ return 'jinja';
17
+ default:
18
+ return 'guidance';
19
+ }
20
+ }
21
+ /**
22
+ * Generate IDN-based script file path
23
+ */
24
+ export function getIdnBasedScriptPath(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn, runnerType) {
25
+ const extension = getExtensionForRunner(runnerType);
26
+ const folderPath = skillFolderPath(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn);
27
+ return path.join(folderPath, `${skillIdn}.${extension}`);
28
+ }
29
+ /**
30
+ * Find all script files in a skill folder
31
+ */
32
+ export async function findSkillScriptFiles(skillFolderPath) {
33
+ if (!(await fs.pathExists(skillFolderPath))) {
34
+ return [];
35
+ }
36
+ const files = await fs.readdir(skillFolderPath);
37
+ const scriptFiles = [];
38
+ for (const fileName of files) {
39
+ const filePath = path.join(skillFolderPath, fileName);
40
+ const stats = await fs.stat(filePath);
41
+ if (stats.isFile()) {
42
+ const ext = path.extname(fileName).toLowerCase();
43
+ // Check for script file extensions
44
+ if (['.jinja', '.guidance', '.nsl'].includes(ext)) {
45
+ const content = await fs.readFile(filePath, 'utf8');
46
+ scriptFiles.push({
47
+ filePath,
48
+ fileName,
49
+ extension: ext.slice(1), // Remove the dot
50
+ content
51
+ });
52
+ }
53
+ }
54
+ }
55
+ return scriptFiles;
56
+ }
57
+ /**
58
+ * Validate skill folder has exactly one script file
59
+ */
60
+ export async function validateSkillFolder(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn) {
61
+ const folderPath = skillFolderPath(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn);
62
+ const files = await findSkillScriptFiles(folderPath);
63
+ const warnings = [];
64
+ const errors = [];
65
+ if (files.length === 0) {
66
+ errors.push(`No script files found in skill folder: ${skillIdn}`);
67
+ }
68
+ else if (files.length > 1) {
69
+ errors.push(`Multiple script files found in skill ${skillIdn}: ${files.map(f => f.fileName).join(', ')}`);
70
+ warnings.push(`Only one script file allowed per skill. Remove extra files and keep one.`);
71
+ }
72
+ return {
73
+ isValid: files.length === 1,
74
+ files,
75
+ warnings,
76
+ errors
77
+ };
78
+ }
79
+ /**
80
+ * Get the single skill script file (if valid)
81
+ */
82
+ export async function getSingleSkillFile(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn) {
83
+ const validation = await validateSkillFolder(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn);
84
+ if (validation.isValid && validation.files.length === 1) {
85
+ return validation.files[0];
86
+ }
87
+ return null;
88
+ }
89
+ /**
90
+ * Check if skill script content is different from target content
91
+ */
92
+ export function isContentDifferent(existingContent, newContent) {
93
+ return sha256(existingContent.trim()) !== sha256(newContent.trim());
94
+ }
95
+ /**
96
+ * Interactive overwrite confirmation
97
+ */
98
+ export async function askForOverwrite(skillIdn, existingFile, newFile) {
99
+ const readline = await import('readline');
100
+ const rl = readline.createInterface({
101
+ input: process.stdin,
102
+ output: process.stdout
103
+ });
104
+ console.log(`\nāš ļø File exists for skill ${skillIdn}:`);
105
+ console.log(` Existing: ${existingFile}`);
106
+ console.log(` New: ${newFile}`);
107
+ const answer = await new Promise((resolve) => {
108
+ rl.question('Overwrite? (y)es/(n)o/(a)ll/(q)uit: ', resolve);
109
+ });
110
+ rl.close();
111
+ const choice = answer.toLowerCase().trim();
112
+ if (choice === 'q' || choice === 'quit') {
113
+ console.log('āŒ Pull operation cancelled by user');
114
+ process.exit(0);
115
+ }
116
+ if (choice === 'a' || choice === 'all') {
117
+ return true; // This should be handled by caller to set global overwrite mode
118
+ }
119
+ return choice === 'y' || choice === 'yes';
120
+ }
121
+ //# sourceMappingURL=skill-files.js.map
@@ -0,0 +1,6 @@
1
+ import type { CustomerConfig } from '../types.js';
2
+ /**
3
+ * Check status of files for a customer
4
+ */
5
+ export declare function status(customer: CustomerConfig, verbose?: boolean): Promise<void>;
6
+ //# sourceMappingURL=status.d.ts.map