newo 1.9.2 → 2.0.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 (66) hide show
  1. package/CHANGELOG.md +116 -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 +298 -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 +43 -0
  38. package/dist/sync/skill-files.js +123 -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 -1226
  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 +372 -0
  62. package/src/sync/push.ts +200 -0
  63. package/src/sync/skill-files.ts +181 -0
  64. package/src/sync/status.ts +277 -0
  65. package/src/sync.ts +14 -1418
  66. package/src/types.ts +0 -1
@@ -0,0 +1,298 @@
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 and overwrite control
52
+ let totalSkills = 0;
53
+ let processedSkills = 0;
54
+ let globalOverwriteAll = silentOverwrite;
55
+ // Count total skills for progress tracking
56
+ for (const project of projects) {
57
+ const agents = await listAgents(client, project.id);
58
+ for (const agent of agents) {
59
+ const flows = agent.flows || [];
60
+ for (const flow of flows) {
61
+ const skills = await listFlowSkills(client, flow.id);
62
+ totalSkills += skills.length;
63
+ }
64
+ }
65
+ }
66
+ if (verbose)
67
+ console.log(`šŸ“Š Total skills to process: ${totalSkills}`);
68
+ for (const project of projects) {
69
+ if (verbose)
70
+ console.log(`šŸ“ Processing project: ${project.title} (${project.idn})`);
71
+ // Create project metadata
72
+ const projectMeta = {
73
+ id: project.id,
74
+ idn: project.idn,
75
+ title: project.title,
76
+ description: project.description || '',
77
+ created_at: project.created_at || '',
78
+ updated_at: project.updated_at || ''
79
+ };
80
+ // Save project metadata
81
+ const projectMetaPath = projectMetadataPath(customer.idn, project.idn);
82
+ const projectMetaYaml = yaml.dump(projectMeta, { indent: 2, quotingType: '"', forceQuotes: false });
83
+ await writeFileSafe(projectMetaPath, projectMetaYaml);
84
+ newHashes[projectMetaPath] = sha256(projectMetaYaml);
85
+ const agents = await listAgents(client, project.id);
86
+ if (verbose)
87
+ console.log(` šŸ“‹ Found ${agents.length} agents in project ${project.title}`);
88
+ const projectData = {
89
+ projectId: project.id,
90
+ projectIdn: project.idn,
91
+ agents: {}
92
+ };
93
+ for (const agent of agents) {
94
+ if (verbose)
95
+ console.log(` šŸ“ Processing agent: ${agent.title} (${agent.idn})`);
96
+ // Create agent metadata
97
+ const agentMeta = {
98
+ id: agent.id,
99
+ idn: agent.idn,
100
+ title: agent.title || '',
101
+ description: agent.description || ''
102
+ };
103
+ // Save agent metadata
104
+ const agentMetaPath = agentMetadataPath(customer.idn, project.idn, agent.idn);
105
+ const agentMetaYaml = yaml.dump(agentMeta, { indent: 2, quotingType: '"', forceQuotes: false });
106
+ await writeFileSafe(agentMetaPath, agentMetaYaml);
107
+ newHashes[agentMetaPath] = sha256(agentMetaYaml);
108
+ projectData.agents[agent.idn] = {
109
+ id: agent.id,
110
+ flows: {}
111
+ };
112
+ const flows = agent.flows || [];
113
+ if (verbose && flows.length > 0) {
114
+ console.log(` šŸ“‹ Found ${flows.length} flows in agent ${agent.title}`);
115
+ }
116
+ for (const flow of flows) {
117
+ if (verbose)
118
+ console.log(` šŸ“ Processing flow: ${flow.title} (${flow.idn})`);
119
+ // Get flow events and states for metadata
120
+ const [events, states] = await Promise.all([
121
+ listFlowEvents(client, flow.id).catch(() => []),
122
+ listFlowStates(client, flow.id).catch(() => [])
123
+ ]);
124
+ // Create flow metadata
125
+ const flowMeta = {
126
+ id: flow.id,
127
+ idn: flow.idn,
128
+ title: flow.title,
129
+ description: flow.description || '',
130
+ default_runner_type: flow.default_runner_type,
131
+ default_model: flow.default_model,
132
+ events,
133
+ state_fields: states
134
+ };
135
+ // Save flow metadata
136
+ const flowMetaPath = flowMetadataPath(customer.idn, project.idn, agent.idn, flow.idn);
137
+ const flowMetaYaml = yaml.dump(flowMeta, { indent: 2, quotingType: '"', forceQuotes: false });
138
+ await writeFileSafe(flowMetaPath, flowMetaYaml);
139
+ newHashes[flowMetaPath] = sha256(flowMetaYaml);
140
+ projectData.agents[agent.idn].flows[flow.idn] = {
141
+ id: flow.id,
142
+ skills: {}
143
+ };
144
+ const skills = await listFlowSkills(client, flow.id);
145
+ if (verbose)
146
+ console.log(` šŸ“‹ Found ${skills.length} skills in flow ${flow.title}`);
147
+ for (const skill of skills) {
148
+ processedSkills++;
149
+ const progress = `[${processedSkills}/${totalSkills}]`;
150
+ if (verbose) {
151
+ console.log(` šŸ“„ ${progress} Processing skill: ${skill.title} (${skill.idn})`);
152
+ }
153
+ else {
154
+ // Show progress for non-verbose mode
155
+ if (processedSkills % 10 === 0 || processedSkills === totalSkills) {
156
+ process.stdout.write(`\ršŸ“„ Processing skills: ${processedSkills}/${totalSkills} (${Math.round(processedSkills / totalSkills * 100)}%)`);
157
+ }
158
+ }
159
+ // Create skill metadata
160
+ const skillMeta = {
161
+ id: skill.id,
162
+ idn: skill.idn,
163
+ title: skill.title,
164
+ runner_type: skill.runner_type,
165
+ model: skill.model,
166
+ parameters: [...skill.parameters],
167
+ path: skill.path
168
+ };
169
+ // Save skill metadata
170
+ const skillMetaPath = skillMetadataPath(customer.idn, project.idn, agent.idn, flow.idn, skill.idn);
171
+ const skillMetaYaml = yaml.dump(skillMeta, { indent: 2, quotingType: '"', forceQuotes: false });
172
+ await writeFileSafe(skillMetaPath, skillMetaYaml);
173
+ newHashes[skillMetaPath] = sha256(skillMetaYaml);
174
+ // Handle skill script with IDN-based naming and overwrite detection
175
+ const scriptContent = skill.prompt_script || '';
176
+ const targetScriptPath = skillScriptPath(customer.idn, project.idn, agent.idn, flow.idn, skill.idn, skill.runner_type);
177
+ const folderPath = skillFolderPath(customer.idn, project.idn, agent.idn, flow.idn, skill.idn);
178
+ // Check for existing script files in the skill folder
179
+ const existingFiles = await findSkillScriptFiles(folderPath);
180
+ let shouldWrite = true;
181
+ let hasContentMatch = false;
182
+ if (existingFiles.length > 0) {
183
+ // Check if any existing file has the same content
184
+ hasContentMatch = existingFiles.some(file => !isContentDifferent(file.content, scriptContent));
185
+ if (hasContentMatch) {
186
+ // Content is the same - handle file naming
187
+ const matchingFile = existingFiles.find(file => !isContentDifferent(file.content, scriptContent));
188
+ const correctName = `${skill.idn}.${getExtensionForRunner(skill.runner_type)}`;
189
+ if (matchingFile && matchingFile.fileName !== correctName) {
190
+ // Remove old file and write with correct IDN-based name
191
+ await fs.remove(matchingFile.filePath);
192
+ if (verbose)
193
+ console.log(` šŸ”„ Renamed ${matchingFile.fileName} → ${correctName}`);
194
+ }
195
+ else if (matchingFile && matchingFile.fileName === correctName) {
196
+ // Already has correct name and content - skip completely
197
+ shouldWrite = false;
198
+ newHashes[matchingFile.filePath] = sha256(scriptContent);
199
+ if (verbose)
200
+ console.log(` āœ“ Content unchanged for ${skill.idn}, keeping existing file`);
201
+ }
202
+ }
203
+ else if (!globalOverwriteAll) {
204
+ // Content is different, ask for overwrite unless global override is set
205
+ const existingFile = existingFiles[0];
206
+ const overwriteChoice = await askForOverwrite(skill.idn, existingFile.fileName, `${skill.idn}.${getExtensionForRunner(skill.runner_type)}`);
207
+ if (overwriteChoice === 'quit') {
208
+ console.log('āŒ Pull operation cancelled by user');
209
+ process.exit(0);
210
+ }
211
+ else if (overwriteChoice === 'all') {
212
+ globalOverwriteAll = true;
213
+ // Continue with overwrite
214
+ for (const file of existingFiles) {
215
+ await fs.remove(file.filePath);
216
+ if (verbose)
217
+ console.log(` šŸ—‘ļø Removed ${file.fileName}`);
218
+ }
219
+ }
220
+ else if (overwriteChoice === 'yes') {
221
+ // Single overwrite
222
+ for (const file of existingFiles) {
223
+ await fs.remove(file.filePath);
224
+ if (verbose)
225
+ console.log(` šŸ—‘ļø Removed ${file.fileName}`);
226
+ }
227
+ }
228
+ else {
229
+ // User said no
230
+ shouldWrite = false;
231
+ if (verbose)
232
+ console.log(` āš ļø Skipped overwrite for ${skill.idn}`);
233
+ }
234
+ }
235
+ else {
236
+ // Silent overwrite mode - remove existing files
237
+ for (const file of existingFiles) {
238
+ await fs.remove(file.filePath);
239
+ if (verbose)
240
+ console.log(` šŸ”„ Silent overwrite: removed ${file.fileName}`);
241
+ }
242
+ }
243
+ }
244
+ if (shouldWrite) {
245
+ await writeFileSafe(targetScriptPath, scriptContent);
246
+ newHashes[targetScriptPath] = sha256(scriptContent);
247
+ const fileName = `${skill.idn}.${getExtensionForRunner(skill.runner_type)}`;
248
+ if (verbose)
249
+ console.log(` āœ“ Saved ${fileName}`);
250
+ }
251
+ projectData.agents[agent.idn].flows[flow.idn].skills[skill.idn] = skillMeta;
252
+ }
253
+ }
254
+ }
255
+ // Store project data in map
256
+ existingMap.projects[project.idn] = projectData;
257
+ }
258
+ // Clear progress line for non-verbose mode
259
+ if (!verbose && totalSkills > 0) {
260
+ console.log(`\nāœ… Processed ${totalSkills} skills`);
261
+ }
262
+ // Save updated project map
263
+ await writeFileSafe(mapFile, JSON.stringify(existingMap, null, 2));
264
+ // Pull customer attributes as part of the project pull
265
+ try {
266
+ if (verbose)
267
+ console.log(`šŸ” Fetching customer attributes for ${customer.idn}...`);
268
+ const attributesContent = await saveCustomerAttributes(client, customer, verbose);
269
+ // Add attributes.yaml hash to the hash store
270
+ const attributesPath = customerAttributesPath(customer.idn);
271
+ newHashes[attributesPath] = sha256(attributesContent);
272
+ if (verbose)
273
+ console.log(`āœ… Customer attributes saved to newo_customers/${customer.idn}/attributes.yaml`);
274
+ }
275
+ catch (error) {
276
+ console.warn(`āš ļø Failed to fetch customer attributes for ${customer.idn}: ${error instanceof Error ? error.message : String(error)}`);
277
+ if (verbose)
278
+ console.warn('You can manually pull attributes using: newo pull-attributes');
279
+ }
280
+ // Generate flows.yaml and get its content for hashing
281
+ const flowsYamlContent = await generateFlowsYaml(existingMap, customer.idn, verbose);
282
+ // Add flows.yaml hash to the hash store
283
+ const flowsYamlFilePath = flowsYamlPath(customer.idn);
284
+ newHashes[flowsYamlFilePath] = sha256(flowsYamlContent);
285
+ // Save hashes (now including flows.yaml and attributes.yaml)
286
+ await saveHashes(newHashes, customer.idn);
287
+ }
288
+ /**
289
+ * Pull all projects for a customer
290
+ */
291
+ export async function pullAll(client, customer, projectId = null, verbose = false, silentOverwrite = false) {
292
+ if (verbose)
293
+ console.log(`šŸ”„ Starting pull operation for customer ${customer.idn}...`);
294
+ await pullSingleProject(client, customer, projectId, verbose, silentOverwrite);
295
+ if (verbose)
296
+ console.log(`āœ… Pull completed for customer ${customer.idn}`);
297
+ }
298
+ //# 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,43 @@
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
+ export type OverwriteChoice = 'yes' | 'no' | 'all' | 'quit';
39
+ /**
40
+ * Interactive overwrite confirmation
41
+ */
42
+ export declare function askForOverwrite(skillIdn: string, existingFile: string, newFile: string): Promise<OverwriteChoice>;
43
+ //# sourceMappingURL=skill-files.d.ts.map
@@ -0,0 +1,123 @@
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
+ return 'quit';
114
+ }
115
+ if (choice === 'a' || choice === 'all') {
116
+ return 'all';
117
+ }
118
+ if (choice === 'y' || choice === 'yes') {
119
+ return 'yes';
120
+ }
121
+ return 'no';
122
+ }
123
+ //# 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