newo 2.0.5 ā 3.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.
- package/CHANGELOG.md +124 -0
- package/README.md +146 -0
- package/dist/api.d.ts +17 -1
- package/dist/api.js +78 -0
- package/dist/cli/commands/create-agent.d.ts +3 -0
- package/dist/cli/commands/create-agent.js +75 -0
- package/dist/cli/commands/create-attribute.d.ts +3 -0
- package/dist/cli/commands/create-attribute.js +63 -0
- package/dist/cli/commands/create-event.d.ts +3 -0
- package/dist/cli/commands/create-event.js +66 -0
- package/dist/cli/commands/create-flow.d.ts +3 -0
- package/dist/cli/commands/create-flow.js +100 -0
- package/dist/cli/commands/create-parameter.d.ts +3 -0
- package/dist/cli/commands/create-parameter.js +47 -0
- package/dist/cli/commands/create-persona.d.ts +3 -0
- package/dist/cli/commands/create-persona.js +43 -0
- package/dist/cli/commands/create-project.d.ts +3 -0
- package/dist/cli/commands/create-project.js +55 -0
- package/dist/cli/commands/create-skill.d.ts +3 -0
- package/dist/cli/commands/create-skill.js +115 -0
- package/dist/cli/commands/create-state.d.ts +3 -0
- package/dist/cli/commands/create-state.js +58 -0
- package/dist/cli/commands/delete-agent.d.ts +3 -0
- package/dist/cli/commands/delete-agent.js +70 -0
- package/dist/cli/commands/delete-flow.d.ts +3 -0
- package/dist/cli/commands/delete-flow.js +83 -0
- package/dist/cli/commands/delete-skill.d.ts +3 -0
- package/dist/cli/commands/delete-skill.js +87 -0
- package/dist/cli/commands/help.js +104 -22
- package/dist/cli/commands/push.js +4 -3
- package/dist/cli.js +48 -0
- package/dist/sync/diff-utils.d.ts +18 -0
- package/dist/sync/diff-utils.js +152 -0
- package/dist/sync/push.d.ts +1 -1
- package/dist/sync/push.js +372 -4
- package/dist/sync/skill-files.js +22 -49
- package/dist/sync/status.js +178 -1
- package/dist/types.d.ts +100 -1
- package/package.json +1 -1
- package/src/api.ts +118 -1
- package/src/cli/commands/create-agent.ts +96 -0
- package/src/cli/commands/create-attribute.ts +75 -0
- package/src/cli/commands/create-event.ts +79 -0
- package/src/cli/commands/create-flow.ts +124 -0
- package/src/cli/commands/create-parameter.ts +59 -0
- package/src/cli/commands/create-persona.ts +54 -0
- package/src/cli/commands/create-project.ts +66 -0
- package/src/cli/commands/create-skill.ts +144 -0
- package/src/cli/commands/create-state.ts +71 -0
- package/src/cli/commands/delete-agent.ts +90 -0
- package/src/cli/commands/delete-flow.ts +105 -0
- package/src/cli/commands/delete-skill.ts +110 -0
- package/src/cli/commands/help.ts +104 -22
- package/src/cli/commands/push.ts +5 -3
- package/src/cli.ts +60 -0
- package/src/sync/diff-utils.ts +168 -0
- package/src/sync/push.ts +413 -5
- package/src/sync/skill-files.ts +22 -57
- package/src/sync/status.ts +183 -1
- package/src/types.ts +122 -2
package/dist/sync/push.js
CHANGED
|
@@ -1,19 +1,131 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Push operations for changed files
|
|
3
3
|
*/
|
|
4
|
-
import { updateSkill } from '../api.js';
|
|
5
|
-
import { ensureState, mapPath, skillMetadataPath } from '../fsutil.js';
|
|
6
|
-
import { validateSkillFolder, getSingleSkillFile } from './skill-files.js';
|
|
4
|
+
import { updateSkill, createAgent, createFlow, createSkill, publishFlow } from '../api.js';
|
|
5
|
+
import { ensureState, mapPath, skillMetadataPath, projectDir, agentMetadataPath } from '../fsutil.js';
|
|
6
|
+
import { validateSkillFolder, getSingleSkillFile, getExtensionForRunner } from './skill-files.js';
|
|
7
7
|
import fs from 'fs-extra';
|
|
8
8
|
import { sha256, loadHashes, saveHashes } from '../hash.js';
|
|
9
9
|
import yaml from 'js-yaml';
|
|
10
10
|
import { generateFlowsYaml } from './metadata.js';
|
|
11
11
|
import { isProjectMap, isLegacyProjectMap } from './projects.js';
|
|
12
12
|
import { flowsYamlPath } from '../fsutil.js';
|
|
13
|
+
/**
|
|
14
|
+
* Scan filesystem for local-only entities not in the project map yet
|
|
15
|
+
*/
|
|
16
|
+
async function scanForLocalOnlyEntities(customer, projects, verbose = false) {
|
|
17
|
+
const localEntities = [];
|
|
18
|
+
let agentCount = 0;
|
|
19
|
+
let flowCount = 0;
|
|
20
|
+
let skillCount = 0;
|
|
21
|
+
// Scan each project directory
|
|
22
|
+
for (const [projectIdn] of Object.entries(projects)) {
|
|
23
|
+
const projDir = projectDir(customer.idn, projectIdn);
|
|
24
|
+
if (!(await fs.pathExists(projDir)))
|
|
25
|
+
continue;
|
|
26
|
+
if (verbose)
|
|
27
|
+
console.log(`š Scanning project directory: ${projDir}`);
|
|
28
|
+
// Get all subdirectories in the project (these should be agents)
|
|
29
|
+
const agentDirs = await fs.readdir(projDir);
|
|
30
|
+
for (const agentIdn of agentDirs) {
|
|
31
|
+
const agentPath = `${projDir}/${agentIdn}`;
|
|
32
|
+
const agentStat = await fs.stat(agentPath);
|
|
33
|
+
// Skip files, only process directories
|
|
34
|
+
if (!agentStat.isDirectory())
|
|
35
|
+
continue;
|
|
36
|
+
// Skip if it's not really an agent directory (no metadata.yaml)
|
|
37
|
+
const agentMetaPath = agentMetadataPath(customer.idn, projectIdn, agentIdn);
|
|
38
|
+
if (!(await fs.pathExists(agentMetaPath)))
|
|
39
|
+
continue;
|
|
40
|
+
// Check if this agent is already in the project map
|
|
41
|
+
const projectData = projects[projectIdn];
|
|
42
|
+
if (!projectData?.agents[agentIdn]) {
|
|
43
|
+
// This is a local-only agent!
|
|
44
|
+
localEntities.push({
|
|
45
|
+
type: 'agent',
|
|
46
|
+
path: agentMetaPath,
|
|
47
|
+
idn: agentIdn,
|
|
48
|
+
projectIdn
|
|
49
|
+
});
|
|
50
|
+
agentCount++;
|
|
51
|
+
if (verbose)
|
|
52
|
+
console.log(` š Found local-only agent: ${agentIdn}`);
|
|
53
|
+
}
|
|
54
|
+
// Now scan for flows within this agent (regardless of whether agent is local-only or not)
|
|
55
|
+
try {
|
|
56
|
+
const flowDirs = await fs.readdir(agentPath);
|
|
57
|
+
for (const flowIdn of flowDirs) {
|
|
58
|
+
const flowPath = `${agentPath}/${flowIdn}`;
|
|
59
|
+
const flowStat = await fs.stat(flowPath);
|
|
60
|
+
// Skip files, only process directories
|
|
61
|
+
if (!flowStat.isDirectory())
|
|
62
|
+
continue;
|
|
63
|
+
// Skip if it's not really a flow directory (no metadata.yaml)
|
|
64
|
+
const flowMetaPath = `${flowPath}/metadata.yaml`;
|
|
65
|
+
if (!(await fs.pathExists(flowMetaPath)))
|
|
66
|
+
continue;
|
|
67
|
+
// Check if this flow exists in the project map
|
|
68
|
+
const agentData = projectData?.agents[agentIdn];
|
|
69
|
+
if (!agentData?.flows[flowIdn]) {
|
|
70
|
+
// This is a local-only flow!
|
|
71
|
+
localEntities.push({
|
|
72
|
+
type: 'flow',
|
|
73
|
+
path: flowMetaPath,
|
|
74
|
+
idn: flowIdn,
|
|
75
|
+
projectIdn,
|
|
76
|
+
agentIdn
|
|
77
|
+
});
|
|
78
|
+
flowCount++;
|
|
79
|
+
if (verbose)
|
|
80
|
+
console.log(` š Found local-only flow: ${agentIdn}/${flowIdn}`);
|
|
81
|
+
}
|
|
82
|
+
// Now scan for skills within this flow (regardless of whether flow is local-only or not)
|
|
83
|
+
try {
|
|
84
|
+
const skillDirs = await fs.readdir(flowPath);
|
|
85
|
+
for (const skillIdn of skillDirs) {
|
|
86
|
+
const skillPath = `${flowPath}/${skillIdn}`;
|
|
87
|
+
const skillStat = await fs.stat(skillPath);
|
|
88
|
+
// Skip files, only process directories
|
|
89
|
+
if (!skillStat.isDirectory())
|
|
90
|
+
continue;
|
|
91
|
+
// Skip if it's not really a skill directory (no metadata.yaml)
|
|
92
|
+
const skillMetaPath = `${skillPath}/metadata.yaml`;
|
|
93
|
+
if (!(await fs.pathExists(skillMetaPath)))
|
|
94
|
+
continue;
|
|
95
|
+
// Check if this skill exists in the project map
|
|
96
|
+
const flowData = agentData?.flows[flowIdn];
|
|
97
|
+
if (!flowData?.skills[skillIdn]) {
|
|
98
|
+
// This is a local-only skill!
|
|
99
|
+
localEntities.push({
|
|
100
|
+
type: 'skill',
|
|
101
|
+
path: skillMetaPath,
|
|
102
|
+
idn: skillIdn,
|
|
103
|
+
projectIdn,
|
|
104
|
+
agentIdn,
|
|
105
|
+
flowIdn
|
|
106
|
+
});
|
|
107
|
+
skillCount++;
|
|
108
|
+
if (verbose)
|
|
109
|
+
console.log(` š Found local-only skill: ${agentIdn}/${flowIdn}/${skillIdn}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
// Ignore errors reading flow directory
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// Ignore errors reading agent directory
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { agentCount, flowCount, skillCount, entities: localEntities };
|
|
124
|
+
}
|
|
13
125
|
/**
|
|
14
126
|
* Push changed files to NEWO platform
|
|
15
127
|
*/
|
|
16
|
-
export async function pushChanged(client, customer, verbose = false) {
|
|
128
|
+
export async function pushChanged(client, customer, verbose = false, shouldPublish = true) {
|
|
17
129
|
await ensureState(customer.idn);
|
|
18
130
|
if (!(await fs.pathExists(mapPath(customer.idn)))) {
|
|
19
131
|
console.log(`No map for customer ${customer.idn}. Run \`newo pull --customer ${customer.idn}\` first.`);
|
|
@@ -33,6 +145,184 @@ export async function pushChanged(client, customer, verbose = false) {
|
|
|
33
145
|
: isLegacyProjectMap(idMapData)
|
|
34
146
|
? { '': idMapData }
|
|
35
147
|
: (() => { throw new Error('Invalid project map format'); })();
|
|
148
|
+
// First, handle any local-only entities (created locally but not yet pushed)
|
|
149
|
+
const localScan = await scanForLocalOnlyEntities(customer, projects, verbose);
|
|
150
|
+
const totalLocalEntities = localScan.agentCount + localScan.flowCount + localScan.skillCount;
|
|
151
|
+
if (totalLocalEntities > 0) {
|
|
152
|
+
console.log(`š¤ Found ${localScan.agentCount} new agent(s), ${localScan.flowCount} new flow(s), ${localScan.skillCount} new skill(s) to create...`);
|
|
153
|
+
// Process in order: agents first, then flows, then skills
|
|
154
|
+
const sortedEntities = localScan.entities.sort((a, b) => {
|
|
155
|
+
const typeOrder = { 'agent': 0, 'flow': 1, 'skill': 2 };
|
|
156
|
+
return typeOrder[a.type] - typeOrder[b.type];
|
|
157
|
+
});
|
|
158
|
+
for (const entity of sortedEntities) {
|
|
159
|
+
if (entity.type === 'agent') {
|
|
160
|
+
try {
|
|
161
|
+
// Read agent metadata
|
|
162
|
+
const metadataContent = await fs.readFile(entity.path, 'utf8');
|
|
163
|
+
const metadata = yaml.load(metadataContent);
|
|
164
|
+
if (verbose)
|
|
165
|
+
console.log(`š¤ Creating agent: ${entity.idn}`);
|
|
166
|
+
// Get project ID from the project map
|
|
167
|
+
const projectData = projects[entity.projectIdn];
|
|
168
|
+
if (!projectData?.projectId) {
|
|
169
|
+
console.error(`ā Project ID not found for project: ${entity.projectIdn}`);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// Create agent on NEWO platform using project-specific v2 endpoint
|
|
173
|
+
const createAgentRequest = {
|
|
174
|
+
idn: metadata.idn,
|
|
175
|
+
title: metadata.title || metadata.idn,
|
|
176
|
+
description: metadata.description || null,
|
|
177
|
+
persona_id: metadata.persona_id || null
|
|
178
|
+
};
|
|
179
|
+
const createResponse = await createAgent(client, projectData.projectId, createAgentRequest);
|
|
180
|
+
console.log(`ā
Agent created: ${entity.idn} (ID: ${createResponse.id})`);
|
|
181
|
+
pushed++;
|
|
182
|
+
metadataChanged = true;
|
|
183
|
+
// Update the metadata with the new ID
|
|
184
|
+
metadata.id = createResponse.id;
|
|
185
|
+
metadata.updated_at = new Date().toISOString();
|
|
186
|
+
const updatedMetadataYaml = yaml.dump(metadata, { indent: 2, quotingType: '"', forceQuotes: false });
|
|
187
|
+
await fs.writeFile(entity.path, updatedMetadataYaml);
|
|
188
|
+
// Update the project map to include the new agent
|
|
189
|
+
if (!projectData.agents[entity.idn]) {
|
|
190
|
+
projectData.agents[entity.idn] = {
|
|
191
|
+
id: createResponse.id,
|
|
192
|
+
flows: {}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
console.error(`ā Failed to create agent ${entity.idn}:`, error.response?.data?.message || error.message);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else if (entity.type === 'flow') {
|
|
201
|
+
try {
|
|
202
|
+
// Read flow metadata
|
|
203
|
+
const metadataContent = await fs.readFile(entity.path, 'utf8');
|
|
204
|
+
const metadata = yaml.load(metadataContent);
|
|
205
|
+
if (verbose)
|
|
206
|
+
console.log(`š¤ Creating flow: ${entity.agentIdn}/${entity.idn}`);
|
|
207
|
+
// Get agent ID from the project map
|
|
208
|
+
const projectData = projects[entity.projectIdn];
|
|
209
|
+
if (!entity.agentIdn) {
|
|
210
|
+
console.error(`ā Agent IDN missing for flow: ${entity.idn}`);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
const agentData = projectData?.agents[entity.agentIdn];
|
|
214
|
+
if (!agentData?.id) {
|
|
215
|
+
console.error(`ā Agent ID not found for agent: ${entity.agentIdn}`);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
// Create flow on NEWO platform
|
|
219
|
+
const createFlowRequest = {
|
|
220
|
+
idn: metadata.idn,
|
|
221
|
+
title: metadata.title || metadata.idn
|
|
222
|
+
};
|
|
223
|
+
const createResponse = await createFlow(client, agentData.id, createFlowRequest);
|
|
224
|
+
console.log(`ā
Flow created: ${entity.idn} (ID: ${createResponse.id})`);
|
|
225
|
+
pushed++;
|
|
226
|
+
metadataChanged = true;
|
|
227
|
+
// Handle the special case where NEWO flow API returns empty response
|
|
228
|
+
if (createResponse.id === 'pending-sync') {
|
|
229
|
+
console.log(`ā
Flow created: ${entity.idn} (ID will be synced on next pull)`);
|
|
230
|
+
// Mark flow as created but pending ID sync
|
|
231
|
+
metadata.id = ''; // Keep empty until sync
|
|
232
|
+
metadata.updated_at = new Date().toISOString();
|
|
233
|
+
const updatedMetadataYaml = yaml.dump(metadata, { indent: 2, quotingType: '"', forceQuotes: false });
|
|
234
|
+
await fs.writeFile(entity.path, updatedMetadataYaml);
|
|
235
|
+
// Update the project map with empty ID (will be filled by pull)
|
|
236
|
+
if (!agentData.flows[entity.idn]) {
|
|
237
|
+
agentData.flows[entity.idn] = {
|
|
238
|
+
id: '', // Empty until synced
|
|
239
|
+
skills: {}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
// Normal case with ID returned
|
|
245
|
+
metadata.id = createResponse.id;
|
|
246
|
+
metadata.updated_at = new Date().toISOString();
|
|
247
|
+
const updatedMetadataYaml = yaml.dump(metadata, { indent: 2, quotingType: '"', forceQuotes: false });
|
|
248
|
+
await fs.writeFile(entity.path, updatedMetadataYaml);
|
|
249
|
+
// Update the project map to include the new flow
|
|
250
|
+
if (!agentData.flows[entity.idn]) {
|
|
251
|
+
agentData.flows[entity.idn] = {
|
|
252
|
+
id: createResponse.id,
|
|
253
|
+
skills: {}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
console.error(`ā Failed to create flow ${entity.idn}:`, error.response?.data?.message || error.message);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else if (entity.type === 'skill') {
|
|
263
|
+
try {
|
|
264
|
+
// Read skill metadata
|
|
265
|
+
const metadataContent = await fs.readFile(entity.path, 'utf8');
|
|
266
|
+
const metadata = yaml.load(metadataContent);
|
|
267
|
+
if (verbose)
|
|
268
|
+
console.log(`š¤ Creating skill: ${entity.agentIdn}/${entity.flowIdn}/${entity.idn}`);
|
|
269
|
+
// Get flow ID from the project map
|
|
270
|
+
const projectData = projects[entity.projectIdn];
|
|
271
|
+
if (!entity.agentIdn || !entity.flowIdn) {
|
|
272
|
+
console.error(`ā Agent IDN or Flow IDN missing for skill: ${entity.idn}`);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const agentData = projectData?.agents[entity.agentIdn];
|
|
276
|
+
const flowData = agentData?.flows[entity.flowIdn];
|
|
277
|
+
if (!flowData?.id) {
|
|
278
|
+
console.error(`ā Flow ID not found for flow: ${entity.flowIdn}`);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
// Read the skill script content
|
|
282
|
+
const skillFolderBase = entity.path.replace('/metadata.yaml', '');
|
|
283
|
+
const scriptExtension = getExtensionForRunner(metadata.runner_type);
|
|
284
|
+
const scriptPath = `${skillFolderBase}/${entity.idn}.${scriptExtension}`;
|
|
285
|
+
let scriptContent = '';
|
|
286
|
+
if (await fs.pathExists(scriptPath)) {
|
|
287
|
+
scriptContent = await fs.readFile(scriptPath, 'utf8');
|
|
288
|
+
}
|
|
289
|
+
// Create skill on NEWO platform
|
|
290
|
+
const createSkillRequest = {
|
|
291
|
+
idn: metadata.idn,
|
|
292
|
+
title: metadata.title || metadata.idn,
|
|
293
|
+
prompt_script: scriptContent,
|
|
294
|
+
runner_type: metadata.runner_type,
|
|
295
|
+
model: metadata.model,
|
|
296
|
+
path: "", // Empty path as shown in curl example
|
|
297
|
+
parameters: metadata.parameters || []
|
|
298
|
+
};
|
|
299
|
+
const createResponse = await createSkill(client, flowData.id, createSkillRequest);
|
|
300
|
+
console.log(`ā
Skill created: ${entity.idn} (ID: ${createResponse.id})`);
|
|
301
|
+
pushed++;
|
|
302
|
+
metadataChanged = true;
|
|
303
|
+
// Update the metadata with the new ID
|
|
304
|
+
metadata.id = createResponse.id;
|
|
305
|
+
metadata.updated_at = new Date().toISOString();
|
|
306
|
+
const updatedMetadataYaml = yaml.dump(metadata, { indent: 2, quotingType: '"', forceQuotes: false });
|
|
307
|
+
await fs.writeFile(entity.path, updatedMetadataYaml);
|
|
308
|
+
// Update the project map to include the new skill
|
|
309
|
+
if (!flowData.skills[entity.idn]) {
|
|
310
|
+
flowData.skills[entity.idn] = {
|
|
311
|
+
id: createResponse.id,
|
|
312
|
+
idn: metadata.idn,
|
|
313
|
+
title: metadata.title || metadata.idn,
|
|
314
|
+
runner_type: metadata.runner_type,
|
|
315
|
+
model: metadata.model,
|
|
316
|
+
parameters: metadata.parameters || []
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
catch (error) {
|
|
321
|
+
console.error(`ā Failed to create skill ${entity.idn}:`, error.response?.data?.message || error.message);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
36
326
|
for (const [projectIdn, projectData] of Object.entries(projects)) {
|
|
37
327
|
if (verbose && projectIdn)
|
|
38
328
|
console.log(`š Checking project: ${projectIdn}`);
|
|
@@ -164,8 +454,86 @@ export async function pushChanged(client, customer, verbose = false) {
|
|
|
164
454
|
const flowsYamlFilePath = flowsYamlPath(customer.idn);
|
|
165
455
|
newHashes[flowsYamlFilePath] = sha256(flowsYamlContent);
|
|
166
456
|
}
|
|
457
|
+
// Save updated project map if metadata changed (new agents added)
|
|
458
|
+
if (metadataChanged) {
|
|
459
|
+
const updatedMapData = isProjectMap(idMapData)
|
|
460
|
+
? { projects }
|
|
461
|
+
: projects['']; // Legacy format
|
|
462
|
+
if (verbose)
|
|
463
|
+
console.log(`š¾ Saving updated project map...`);
|
|
464
|
+
await fs.writeJson(mapPath(customer.idn), updatedMapData, { spaces: 2 });
|
|
465
|
+
}
|
|
167
466
|
// Save updated hashes
|
|
168
467
|
await saveHashes(newHashes, customer.idn);
|
|
169
468
|
console.log(pushed ? `${pushed} file(s) pushed.` : 'No changes to push.');
|
|
469
|
+
// Publish flows if requested (default behavior)
|
|
470
|
+
if (shouldPublish && pushed > 0) {
|
|
471
|
+
if (verbose)
|
|
472
|
+
console.log('\nš Publishing flows...');
|
|
473
|
+
let publishedFlows = 0;
|
|
474
|
+
let failedFlows = 0;
|
|
475
|
+
const publishErrors = [];
|
|
476
|
+
for (const [, projectData] of Object.entries(projects)) {
|
|
477
|
+
for (const [, agentObj] of Object.entries(projectData.agents)) {
|
|
478
|
+
for (const [flowIdn, flowObj] of Object.entries(agentObj.flows)) {
|
|
479
|
+
if (flowObj.id) {
|
|
480
|
+
try {
|
|
481
|
+
const publishData = {
|
|
482
|
+
version: "1.0",
|
|
483
|
+
description: "Published via NEWO CLI",
|
|
484
|
+
type: "public"
|
|
485
|
+
};
|
|
486
|
+
await publishFlow(client, flowObj.id, publishData);
|
|
487
|
+
if (verbose)
|
|
488
|
+
console.log(`š¤ Published flow: ${flowIdn} (${flowObj.id})`);
|
|
489
|
+
publishedFlows++;
|
|
490
|
+
}
|
|
491
|
+
catch (error) {
|
|
492
|
+
failedFlows++;
|
|
493
|
+
// Extract detailed error information from API response
|
|
494
|
+
const errorMessage = error.response?.data?.message || error.message || 'Unknown error';
|
|
495
|
+
const errorDetails = error.response?.data?.reasons || error.response?.data?.errors || error.response?.data?.detail;
|
|
496
|
+
publishErrors.push({
|
|
497
|
+
flowIdn,
|
|
498
|
+
error: errorMessage,
|
|
499
|
+
details: errorDetails
|
|
500
|
+
});
|
|
501
|
+
// Always show publish errors (not just in verbose mode)
|
|
502
|
+
console.error(`ā Failed to publish flow '${flowIdn}': ${errorMessage}`);
|
|
503
|
+
if (errorDetails) {
|
|
504
|
+
if (Array.isArray(errorDetails)) {
|
|
505
|
+
console.error(` Reasons:`);
|
|
506
|
+
errorDetails.forEach((reason) => {
|
|
507
|
+
console.error(` ⢠${reason}`);
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
else if (typeof errorDetails === 'object') {
|
|
511
|
+
console.error(` Details: ${JSON.stringify(errorDetails, null, 2)}`);
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
console.error(` Details: ${errorDetails}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
// Summary message
|
|
523
|
+
if (publishedFlows > 0 || failedFlows > 0) {
|
|
524
|
+
console.log(`\nš Publish summary: ${publishedFlows} succeeded, ${failedFlows} failed.`);
|
|
525
|
+
if (failedFlows > 0) {
|
|
526
|
+
console.log(`\nā ļø ${failedFlows} flow(s) failed to publish due to validation errors.`);
|
|
527
|
+
console.log(` Fix the errors above and run 'npm run push' again.`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
else if (verbose) {
|
|
531
|
+
console.log('\nš” No flows to publish.');
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
// If we created flows, recommend a pull to sync flow IDs
|
|
535
|
+
if (localScan.flowCount > 0) {
|
|
536
|
+
console.log('\nš” Tip: Run "newo pull" to sync flow IDs and enable skill creation.');
|
|
537
|
+
}
|
|
170
538
|
}
|
|
171
539
|
//# sourceMappingURL=push.js.map
|
package/dist/sync/skill-files.js
CHANGED
|
@@ -107,62 +107,35 @@ export async function askForOverwrite(skillIdn, existingContent, newContent, fil
|
|
|
107
107
|
const greenBg = '\x1b[102m\x1b[30m'; // Light green background, black text (like GitHub)
|
|
108
108
|
const gray = '\x1b[90m';
|
|
109
109
|
const reset = '\x1b[0m';
|
|
110
|
-
//
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
let diffEnd = -1;
|
|
116
|
-
// Find first difference
|
|
117
|
-
for (let i = 0; i < Math.max(localLines.length, remoteLines.length); i++) {
|
|
118
|
-
if (localLines[i] !== remoteLines[i]) {
|
|
119
|
-
diffStart = i;
|
|
120
|
-
break;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (diffStart === -1) {
|
|
124
|
-
// No differences found (shouldn't happen, but handle gracefully)
|
|
110
|
+
// Generate proper diff using LCS algorithm
|
|
111
|
+
const { generateDiff, filterDiffWithContext } = await import('./diff-utils.js');
|
|
112
|
+
const fullDiff = generateDiff(existingContent, newContent);
|
|
113
|
+
const contextDiff = filterDiffWithContext(fullDiff, 2);
|
|
114
|
+
if (contextDiff.length === 0) {
|
|
125
115
|
console.log(`${gray} No differences found${reset}`);
|
|
126
116
|
return 'no';
|
|
127
117
|
}
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
diffEnd++;
|
|
135
|
-
}
|
|
136
|
-
// Show context before (2 lines)
|
|
137
|
-
const contextStart = Math.max(0, diffStart - 2);
|
|
138
|
-
for (let i = contextStart; i < diffStart; i++) {
|
|
139
|
-
if (localLines[i] !== undefined) {
|
|
140
|
-
console.log(` ${String(i + 1).padStart(3)} ${localLines[i]}`);
|
|
118
|
+
// Display the diff with proper GitHub-style formatting
|
|
119
|
+
for (const line of contextDiff) {
|
|
120
|
+
if (line.type === 'context') {
|
|
121
|
+
// Show context lines in gray
|
|
122
|
+
const lineNum = line.localLineNum !== -1 ? line.localLineNum : line.remoteLineNum;
|
|
123
|
+
console.log(` ${String(lineNum).padStart(3)} ${line.content}`);
|
|
141
124
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
for (let i = diffStart; i < maxDiffLine; i++) {
|
|
146
|
-
const localLine = localLines[i];
|
|
147
|
-
const remoteLine = remoteLines[i];
|
|
148
|
-
if (localLine !== undefined && (remoteLine === undefined || localLine !== remoteLine)) {
|
|
149
|
-
console.log(`${redBg} - ${String(i + 1).padStart(3)} ${localLine} ${reset}`);
|
|
150
|
-
}
|
|
151
|
-
if (remoteLine !== undefined && (localLine === undefined || localLine !== remoteLine)) {
|
|
152
|
-
console.log(`${greenBg} + ${String(i + 1).padStart(3)} ${remoteLine} ${reset}`);
|
|
125
|
+
else if (line.type === 'remove') {
|
|
126
|
+
// Show local content being removed (red background)
|
|
127
|
+
console.log(`${redBg} - ${String(line.localLineNum).padStart(3)} ${line.content} ${reset}`);
|
|
153
128
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
for (let i = diffEnd; i < contextEnd; i++) {
|
|
158
|
-
if (localLines[i] !== undefined) {
|
|
159
|
-
console.log(` ${String(i + 1).padStart(3)} ${localLines[i]}`);
|
|
129
|
+
else if (line.type === 'add') {
|
|
130
|
+
// Show remote content being added (green background)
|
|
131
|
+
console.log(`${greenBg} + ${String(line.remoteLineNum).padStart(3)} ${line.content} ${reset}`);
|
|
160
132
|
}
|
|
161
133
|
}
|
|
162
|
-
// Show if there are more
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
134
|
+
// Show if there are more changes beyond what we're displaying
|
|
135
|
+
const totalChanges = fullDiff.filter(line => line.type !== 'context').length;
|
|
136
|
+
const displayedChanges = contextDiff.filter(line => line.type !== 'context').length;
|
|
137
|
+
if (totalChanges > displayedChanges) {
|
|
138
|
+
console.log(`${gray}... (${totalChanges - displayedChanges} more changes)${reset}`);
|
|
166
139
|
}
|
|
167
140
|
const answer = await new Promise((resolve) => {
|
|
168
141
|
rl.question(`\nReplace local with remote? (y)es/(n)o/(a)ll/(q)uit: `, resolve);
|