newo 1.4.0 → 1.5.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/.env.example +17 -6
- package/CHANGELOG.md +102 -0
- package/README.md +96 -1
- package/dist/akb.d.ts +10 -0
- package/dist/akb.js +88 -0
- package/dist/api.d.ts +14 -0
- package/dist/api.js +103 -0
- package/dist/auth.d.ts +6 -0
- package/dist/auth.js +361 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +307 -0
- package/dist/customer.d.ts +23 -0
- package/dist/customer.js +87 -0
- package/dist/customerAsync.d.ts +22 -0
- package/dist/customerAsync.js +67 -0
- package/dist/customerInit.d.ts +10 -0
- package/dist/customerInit.js +78 -0
- package/dist/env.d.ts +33 -0
- package/dist/env.js +82 -0
- package/dist/fsutil.d.ts +20 -0
- package/dist/fsutil.js +51 -0
- package/dist/hash.d.ts +5 -0
- package/dist/hash.js +40 -0
- package/dist/sync.d.ts +7 -0
- package/dist/sync.js +376 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.js +5 -0
- package/package.json +35 -14
- package/src/{akb.js → akb.ts} +35 -39
- package/src/api.ts +130 -0
- package/src/auth.ts +415 -0
- package/src/cli.ts +316 -0
- package/src/customer.ts +102 -0
- package/src/customerAsync.ts +78 -0
- package/src/customerInit.ts +97 -0
- package/src/env.ts +118 -0
- package/src/fsutil.ts +73 -0
- package/src/hash.ts +41 -0
- package/src/{sync.js → sync.ts} +179 -81
- package/src/types.ts +276 -0
- package/src/api.js +0 -103
- package/src/auth.js +0 -92
- package/src/cli.js +0 -108
- package/src/fsutil.js +0 -34
- package/src/hash.js +0 -17
package/dist/sync.js
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { listProjects, listAgents, listFlowSkills, updateSkill, listFlowEvents, listFlowStates, getProjectMeta } from './api.js';
|
|
2
|
+
import { ensureState, skillPath, writeFileSafe, readIfExists, mapPath, metadataPath, flowsYamlPath } from './fsutil.js';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import { sha256, loadHashes, saveHashes } from './hash.js';
|
|
5
|
+
import yaml from 'js-yaml';
|
|
6
|
+
import pLimit from 'p-limit';
|
|
7
|
+
// Concurrency limits for API operations
|
|
8
|
+
const concurrencyLimit = pLimit(5);
|
|
9
|
+
// Type guards for better type safety
|
|
10
|
+
function isProjectMap(x) {
|
|
11
|
+
return !!x && typeof x === 'object' && 'projects' in x;
|
|
12
|
+
}
|
|
13
|
+
function isLegacyProjectMap(x) {
|
|
14
|
+
return !!x && typeof x === 'object' && 'agents' in x;
|
|
15
|
+
}
|
|
16
|
+
export async function pullSingleProject(client, customer, projectId, projectIdn, verbose = false) {
|
|
17
|
+
if (verbose)
|
|
18
|
+
console.log(`🔍 Fetching agents for project ${projectId} (${projectIdn}) for customer ${customer.idn}...`);
|
|
19
|
+
const agents = await listAgents(client, projectId);
|
|
20
|
+
if (verbose)
|
|
21
|
+
console.log(`📦 Found ${agents.length} agents`);
|
|
22
|
+
// Get and save project metadata
|
|
23
|
+
const projectMeta = await getProjectMeta(client, projectId);
|
|
24
|
+
await writeFileSafe(metadataPath(customer.idn, projectIdn), JSON.stringify(projectMeta, null, 2));
|
|
25
|
+
if (verbose)
|
|
26
|
+
console.log(`✓ Saved metadata for ${projectIdn}`);
|
|
27
|
+
const projectMap = { projectId, projectIdn, agents: {} };
|
|
28
|
+
for (const agent of agents) {
|
|
29
|
+
const aKey = agent.idn;
|
|
30
|
+
projectMap.agents[aKey] = { id: agent.id, flows: {} };
|
|
31
|
+
for (const flow of agent.flows ?? []) {
|
|
32
|
+
projectMap.agents[aKey].flows[flow.idn] = { id: flow.id, skills: {} };
|
|
33
|
+
const skills = await listFlowSkills(client, flow.id);
|
|
34
|
+
// Process skills concurrently with limited concurrency
|
|
35
|
+
await Promise.all(skills.map(skill => concurrencyLimit(async () => {
|
|
36
|
+
const file = skillPath(customer.idn, projectIdn, agent.idn, flow.idn, skill.idn, skill.runner_type);
|
|
37
|
+
await writeFileSafe(file, skill.prompt_script || '');
|
|
38
|
+
// Store complete skill metadata for push operations
|
|
39
|
+
projectMap.agents[aKey].flows[flow.idn].skills[skill.idn] = {
|
|
40
|
+
id: skill.id,
|
|
41
|
+
title: skill.title,
|
|
42
|
+
idn: skill.idn,
|
|
43
|
+
runner_type: skill.runner_type,
|
|
44
|
+
model: skill.model,
|
|
45
|
+
parameters: [...skill.parameters],
|
|
46
|
+
path: skill.path || undefined
|
|
47
|
+
};
|
|
48
|
+
console.log(`✓ Pulled ${file}`);
|
|
49
|
+
})));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Generate flows.yaml for this project
|
|
53
|
+
if (verbose)
|
|
54
|
+
console.log(`📄 Generating flows.yaml...`);
|
|
55
|
+
await generateFlowsYaml(client, customer, agents, verbose);
|
|
56
|
+
return projectMap;
|
|
57
|
+
}
|
|
58
|
+
export async function pullAll(client, customer, projectId = null, verbose = false) {
|
|
59
|
+
await ensureState(customer.idn);
|
|
60
|
+
if (projectId) {
|
|
61
|
+
// Single project mode
|
|
62
|
+
const projectMeta = await getProjectMeta(client, projectId);
|
|
63
|
+
const projectMap = await pullSingleProject(client, customer, projectId, projectMeta.idn, verbose);
|
|
64
|
+
const idMap = { projects: { [projectMeta.idn]: projectMap } };
|
|
65
|
+
await fs.writeJson(mapPath(customer.idn), idMap, { spaces: 2 });
|
|
66
|
+
// Generate hash tracking for this project
|
|
67
|
+
const hashes = {};
|
|
68
|
+
for (const [agentIdn, agentObj] of Object.entries(projectMap.agents)) {
|
|
69
|
+
for (const [flowIdn, flowObj] of Object.entries(agentObj.flows)) {
|
|
70
|
+
for (const [skillIdn, skillMeta] of Object.entries(flowObj.skills)) {
|
|
71
|
+
const p = skillPath(customer.idn, projectMeta.idn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
|
|
72
|
+
const content = await fs.readFile(p, 'utf8');
|
|
73
|
+
hashes[p] = sha256(content);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
await saveHashes(hashes, customer.idn);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
// Multi-project mode
|
|
81
|
+
if (verbose)
|
|
82
|
+
console.log(`🔍 Fetching all projects for customer ${customer.idn}...`);
|
|
83
|
+
const projects = await listProjects(client);
|
|
84
|
+
if (verbose)
|
|
85
|
+
console.log(`📦 Found ${projects.length} projects`);
|
|
86
|
+
const idMap = { projects: {} };
|
|
87
|
+
const allHashes = {};
|
|
88
|
+
for (const project of projects) {
|
|
89
|
+
if (verbose)
|
|
90
|
+
console.log(`\n📁 Processing project: ${project.idn} (${project.title})`);
|
|
91
|
+
const projectMap = await pullSingleProject(client, customer, project.id, project.idn, verbose);
|
|
92
|
+
idMap.projects[project.idn] = projectMap;
|
|
93
|
+
// Collect hashes for this project
|
|
94
|
+
for (const [agentIdn, agentObj] of Object.entries(projectMap.agents)) {
|
|
95
|
+
for (const [flowIdn, flowObj] of Object.entries(agentObj.flows)) {
|
|
96
|
+
for (const [skillIdn, skillMeta] of Object.entries(flowObj.skills)) {
|
|
97
|
+
const p = skillPath(customer.idn, project.idn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
|
|
98
|
+
const content = await fs.readFile(p, 'utf8');
|
|
99
|
+
allHashes[p] = sha256(content);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
await fs.writeJson(mapPath(customer.idn), idMap, { spaces: 2 });
|
|
105
|
+
await saveHashes(allHashes, customer.idn);
|
|
106
|
+
}
|
|
107
|
+
export async function pushChanged(client, customer, verbose = false) {
|
|
108
|
+
await ensureState(customer.idn);
|
|
109
|
+
if (!(await fs.pathExists(mapPath(customer.idn)))) {
|
|
110
|
+
throw new Error(`Missing .newo/${customer.idn}/map.json. Run \`newo pull --customer ${customer.idn}\` first.`);
|
|
111
|
+
}
|
|
112
|
+
if (verbose)
|
|
113
|
+
console.log(`📋 Loading project mapping for customer ${customer.idn}...`);
|
|
114
|
+
const idMapData = await fs.readJson(mapPath(customer.idn));
|
|
115
|
+
if (verbose)
|
|
116
|
+
console.log('🔍 Loading file hashes...');
|
|
117
|
+
const oldHashes = await loadHashes(customer.idn);
|
|
118
|
+
const newHashes = { ...oldHashes };
|
|
119
|
+
if (verbose)
|
|
120
|
+
console.log('🔄 Scanning for changes...');
|
|
121
|
+
let pushed = 0;
|
|
122
|
+
let scanned = 0;
|
|
123
|
+
// Handle both old single-project format and new multi-project format with type guards
|
|
124
|
+
const projects = isProjectMap(idMapData) && idMapData.projects
|
|
125
|
+
? idMapData.projects
|
|
126
|
+
: isLegacyProjectMap(idMapData)
|
|
127
|
+
? { '': idMapData }
|
|
128
|
+
: (() => { throw new Error('Invalid project map format'); })();
|
|
129
|
+
for (const [projectIdn, projectData] of Object.entries(projects)) {
|
|
130
|
+
if (verbose && projectIdn)
|
|
131
|
+
console.log(`📁 Scanning project: ${projectIdn}`);
|
|
132
|
+
for (const [agentIdn, agentObj] of Object.entries(projectData.agents)) {
|
|
133
|
+
if (verbose)
|
|
134
|
+
console.log(` 📁 Scanning agent: ${agentIdn}`);
|
|
135
|
+
for (const [flowIdn, flowObj] of Object.entries(agentObj.flows)) {
|
|
136
|
+
if (verbose)
|
|
137
|
+
console.log(` 📁 Scanning flow: ${flowIdn}`);
|
|
138
|
+
for (const [skillIdn, skillMeta] of Object.entries(flowObj.skills)) {
|
|
139
|
+
const p = projectIdn ?
|
|
140
|
+
skillPath(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type) :
|
|
141
|
+
skillPath(customer.idn, '', agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
|
|
142
|
+
scanned++;
|
|
143
|
+
if (verbose)
|
|
144
|
+
console.log(` 📄 Checking: ${p}`);
|
|
145
|
+
const content = await readIfExists(p);
|
|
146
|
+
if (content === null) {
|
|
147
|
+
if (verbose)
|
|
148
|
+
console.log(` ⚠️ File not found: ${p}`);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
const h = sha256(content);
|
|
152
|
+
const oldHash = oldHashes[p];
|
|
153
|
+
if (verbose) {
|
|
154
|
+
console.log(` 🔍 Hash comparison:`);
|
|
155
|
+
console.log(` Old: ${oldHash || 'none'}`);
|
|
156
|
+
console.log(` New: ${h}`);
|
|
157
|
+
}
|
|
158
|
+
if (oldHash !== h) {
|
|
159
|
+
if (verbose)
|
|
160
|
+
console.log(` 🔄 File changed, preparing to push...`);
|
|
161
|
+
// Create complete skill object with updated prompt_script
|
|
162
|
+
const skillObject = {
|
|
163
|
+
id: skillMeta.id,
|
|
164
|
+
title: skillMeta.title,
|
|
165
|
+
idn: skillMeta.idn,
|
|
166
|
+
prompt_script: content,
|
|
167
|
+
runner_type: skillMeta.runner_type,
|
|
168
|
+
model: skillMeta.model,
|
|
169
|
+
parameters: skillMeta.parameters,
|
|
170
|
+
path: skillMeta.path || undefined
|
|
171
|
+
};
|
|
172
|
+
if (verbose) {
|
|
173
|
+
console.log(` 📤 Pushing skill object:`);
|
|
174
|
+
console.log(` ID: ${skillObject.id}`);
|
|
175
|
+
console.log(` Title: ${skillObject.title}`);
|
|
176
|
+
console.log(` IDN: ${skillObject.idn}`);
|
|
177
|
+
console.log(` Content length: ${content.length} chars`);
|
|
178
|
+
console.log(` Content preview: ${content.substring(0, 100).replace(/\n/g, '\\n')}...`);
|
|
179
|
+
}
|
|
180
|
+
await updateSkill(client, skillObject);
|
|
181
|
+
console.log(`↑ Pushed ${p}`);
|
|
182
|
+
newHashes[p] = h;
|
|
183
|
+
pushed++;
|
|
184
|
+
}
|
|
185
|
+
else if (verbose) {
|
|
186
|
+
console.log(` ✓ No changes`);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (verbose)
|
|
193
|
+
console.log(`🔄 Scanned ${scanned} files, found ${pushed} changes`);
|
|
194
|
+
await saveHashes(newHashes, customer.idn);
|
|
195
|
+
console.log(pushed ? `✅ Push complete. ${pushed} file(s) updated.` : '✅ Nothing to push.');
|
|
196
|
+
}
|
|
197
|
+
export async function status(customer, verbose = false) {
|
|
198
|
+
await ensureState(customer.idn);
|
|
199
|
+
if (!(await fs.pathExists(mapPath(customer.idn)))) {
|
|
200
|
+
console.log(`No map for customer ${customer.idn}. Run \`newo pull --customer ${customer.idn}\` first.`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (verbose)
|
|
204
|
+
console.log(`📋 Loading project mapping and hashes for customer ${customer.idn}...`);
|
|
205
|
+
const idMapData = await fs.readJson(mapPath(customer.idn));
|
|
206
|
+
const hashes = await loadHashes(customer.idn);
|
|
207
|
+
let dirty = 0;
|
|
208
|
+
// Handle both old single-project format and new multi-project format with type guards
|
|
209
|
+
const projects = isProjectMap(idMapData) && idMapData.projects
|
|
210
|
+
? idMapData.projects
|
|
211
|
+
: isLegacyProjectMap(idMapData)
|
|
212
|
+
? { '': idMapData }
|
|
213
|
+
: (() => { throw new Error('Invalid project map format'); })();
|
|
214
|
+
for (const [projectIdn, projectData] of Object.entries(projects)) {
|
|
215
|
+
if (verbose && projectIdn)
|
|
216
|
+
console.log(`📁 Checking project: ${projectIdn}`);
|
|
217
|
+
for (const [agentIdn, agentObj] of Object.entries(projectData.agents)) {
|
|
218
|
+
if (verbose)
|
|
219
|
+
console.log(` 📁 Checking agent: ${agentIdn}`);
|
|
220
|
+
for (const [flowIdn, flowObj] of Object.entries(agentObj.flows)) {
|
|
221
|
+
if (verbose)
|
|
222
|
+
console.log(` 📁 Checking flow: ${flowIdn}`);
|
|
223
|
+
for (const [skillIdn, skillMeta] of Object.entries(flowObj.skills)) {
|
|
224
|
+
const p = projectIdn ?
|
|
225
|
+
skillPath(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type) :
|
|
226
|
+
skillPath(customer.idn, '', agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
|
|
227
|
+
const exists = await fs.pathExists(p);
|
|
228
|
+
if (!exists) {
|
|
229
|
+
console.log(`D ${p}`);
|
|
230
|
+
dirty++;
|
|
231
|
+
if (verbose)
|
|
232
|
+
console.log(` ❌ Deleted: ${p}`);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
const content = await fs.readFile(p, 'utf8');
|
|
236
|
+
const h = sha256(content);
|
|
237
|
+
const oldHash = hashes[p];
|
|
238
|
+
if (verbose) {
|
|
239
|
+
console.log(` 📄 ${p}`);
|
|
240
|
+
console.log(` Old hash: ${oldHash || 'none'}`);
|
|
241
|
+
console.log(` New hash: ${h}`);
|
|
242
|
+
}
|
|
243
|
+
if (oldHash !== h) {
|
|
244
|
+
console.log(`M ${p}`);
|
|
245
|
+
dirty++;
|
|
246
|
+
if (verbose)
|
|
247
|
+
console.log(` 🔄 Modified: ${p}`);
|
|
248
|
+
}
|
|
249
|
+
else if (verbose) {
|
|
250
|
+
console.log(` ✓ Unchanged: ${p}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
console.log(dirty ? `${dirty} changed file(s).` : 'Clean.');
|
|
257
|
+
}
|
|
258
|
+
async function generateFlowsYaml(client, customer, agents, verbose = false) {
|
|
259
|
+
const flowsData = { flows: [] };
|
|
260
|
+
// Calculate total flows for progress tracking
|
|
261
|
+
const totalFlows = agents.reduce((sum, agent) => sum + (agent.flows?.length || 0), 0);
|
|
262
|
+
let processedFlows = 0;
|
|
263
|
+
if (!verbose && totalFlows > 0) {
|
|
264
|
+
console.log(`📄 Generating flows.yaml (${totalFlows} flows)...`);
|
|
265
|
+
}
|
|
266
|
+
for (const agent of agents) {
|
|
267
|
+
if (verbose)
|
|
268
|
+
console.log(` 📁 Processing agent: ${agent.idn}`);
|
|
269
|
+
const agentFlows = [];
|
|
270
|
+
for (const flow of agent.flows ?? []) {
|
|
271
|
+
processedFlows++;
|
|
272
|
+
if (verbose) {
|
|
273
|
+
console.log(` 📄 Processing flow: ${flow.idn}`);
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
// Simple progress indicator without verbose mode
|
|
277
|
+
const percent = Math.round((processedFlows / totalFlows) * 100);
|
|
278
|
+
const progressBar = '█'.repeat(Math.floor(percent / 5)) + '░'.repeat(20 - Math.floor(percent / 5));
|
|
279
|
+
const progressText = ` [${progressBar}] ${percent}% (${processedFlows}/${totalFlows}) ${flow.idn}`;
|
|
280
|
+
// Pad the line to clear any leftover text from longer previous lines
|
|
281
|
+
const padding = ' '.repeat(Math.max(0, 80 - progressText.length));
|
|
282
|
+
process.stdout.write(`\r${progressText}${padding}`);
|
|
283
|
+
}
|
|
284
|
+
// Get skills for this flow
|
|
285
|
+
const skills = await listFlowSkills(client, flow.id);
|
|
286
|
+
const skillsData = skills.map(skill => ({
|
|
287
|
+
idn: skill.idn,
|
|
288
|
+
title: skill.title || "",
|
|
289
|
+
prompt_script: `flows/${flow.idn}/${skill.idn}.${skill.runner_type === 'nsl' ? 'jinja' : 'guidance'}`,
|
|
290
|
+
runner_type: `!enum "RunnerType.${skill.runner_type}"`,
|
|
291
|
+
model: {
|
|
292
|
+
model_idn: skill.model.model_idn,
|
|
293
|
+
provider_idn: skill.model.provider_idn
|
|
294
|
+
},
|
|
295
|
+
parameters: skill.parameters.map(param => ({
|
|
296
|
+
name: param.name,
|
|
297
|
+
default_value: param.default_value || " "
|
|
298
|
+
}))
|
|
299
|
+
}));
|
|
300
|
+
// Get events for this flow
|
|
301
|
+
let eventsData = [];
|
|
302
|
+
try {
|
|
303
|
+
const events = await listFlowEvents(client, flow.id);
|
|
304
|
+
eventsData = events.map(event => ({
|
|
305
|
+
title: event.description,
|
|
306
|
+
idn: event.idn,
|
|
307
|
+
skill_selector: `!enum "SkillSelector.${event.skill_selector}"`,
|
|
308
|
+
skill_idn: event.skill_idn || undefined,
|
|
309
|
+
state_idn: event.state_idn || undefined,
|
|
310
|
+
integration_idn: event.integration_idn || undefined,
|
|
311
|
+
connector_idn: event.connector_idn || undefined,
|
|
312
|
+
interrupt_mode: `!enum "InterruptMode.${event.interrupt_mode}"`
|
|
313
|
+
}));
|
|
314
|
+
if (verbose)
|
|
315
|
+
console.log(` 📋 Found ${events.length} events`);
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
if (verbose)
|
|
319
|
+
console.log(` ⚠️ No events found for flow ${flow.idn}`);
|
|
320
|
+
}
|
|
321
|
+
// Get state fields for this flow
|
|
322
|
+
let stateFieldsData = [];
|
|
323
|
+
try {
|
|
324
|
+
const states = await listFlowStates(client, flow.id);
|
|
325
|
+
stateFieldsData = states.map(state => ({
|
|
326
|
+
title: state.title,
|
|
327
|
+
idn: state.idn,
|
|
328
|
+
default_value: state.default_value || undefined,
|
|
329
|
+
scope: `!enum "StateFieldScope.${state.scope}"`
|
|
330
|
+
}));
|
|
331
|
+
if (verbose)
|
|
332
|
+
console.log(` 📊 Found ${states.length} state fields`);
|
|
333
|
+
}
|
|
334
|
+
catch (error) {
|
|
335
|
+
if (verbose)
|
|
336
|
+
console.log(` ⚠️ No state fields found for flow ${flow.idn}`);
|
|
337
|
+
}
|
|
338
|
+
agentFlows.push({
|
|
339
|
+
idn: flow.idn,
|
|
340
|
+
title: flow.title,
|
|
341
|
+
description: flow.description || null,
|
|
342
|
+
default_runner_type: `!enum "RunnerType.${flow.default_runner_type}"`,
|
|
343
|
+
default_provider_idn: flow.default_model.provider_idn,
|
|
344
|
+
default_model_idn: flow.default_model.model_idn,
|
|
345
|
+
skills: skillsData,
|
|
346
|
+
events: eventsData,
|
|
347
|
+
state_fields: stateFieldsData
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
const agentData = {
|
|
351
|
+
agent_idn: agent.idn,
|
|
352
|
+
agent_description: agent.description || undefined,
|
|
353
|
+
agent_flows: agentFlows
|
|
354
|
+
};
|
|
355
|
+
flowsData.flows.push(agentData);
|
|
356
|
+
}
|
|
357
|
+
// Clear progress bar and move to new line
|
|
358
|
+
if (!verbose && totalFlows > 0) {
|
|
359
|
+
process.stdout.write('\n');
|
|
360
|
+
}
|
|
361
|
+
// Convert to YAML and write to file with custom enum handling
|
|
362
|
+
let yamlContent = yaml.dump(flowsData, {
|
|
363
|
+
indent: 2,
|
|
364
|
+
lineWidth: -1,
|
|
365
|
+
noRefs: true,
|
|
366
|
+
sortKeys: false,
|
|
367
|
+
quotingType: '"',
|
|
368
|
+
forceQuotes: false
|
|
369
|
+
});
|
|
370
|
+
// Post-process to fix enum formatting
|
|
371
|
+
yamlContent = yamlContent.replace(/"(!enum "[^"]+")"/g, '$1');
|
|
372
|
+
const yamlPath = flowsYamlPath(customer.idn);
|
|
373
|
+
await writeFileSafe(yamlPath, yamlContent);
|
|
374
|
+
console.log(`✓ Generated flows.yaml`);
|
|
375
|
+
}
|
|
376
|
+
//# sourceMappingURL=sync.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comprehensive type definitions for NEWO CLI
|
|
3
|
+
*/
|
|
4
|
+
export interface NewoEnvironment {
|
|
5
|
+
NEWO_BASE_URL?: string;
|
|
6
|
+
NEWO_PROJECT_ID?: string;
|
|
7
|
+
NEWO_API_KEY?: string;
|
|
8
|
+
NEWO_API_KEYS?: string;
|
|
9
|
+
NEWO_ACCESS_TOKEN?: string;
|
|
10
|
+
NEWO_REFRESH_TOKEN?: string;
|
|
11
|
+
NEWO_REFRESH_URL?: string;
|
|
12
|
+
NEWO_DEFAULT_CUSTOMER?: string;
|
|
13
|
+
[key: string]: string | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface ApiKeyConfig {
|
|
16
|
+
key: string;
|
|
17
|
+
project_id?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface CustomerConfig {
|
|
20
|
+
idn: string;
|
|
21
|
+
apiKey: string;
|
|
22
|
+
projectId?: string | undefined;
|
|
23
|
+
}
|
|
24
|
+
export interface CustomerProfile {
|
|
25
|
+
id: string;
|
|
26
|
+
idn: string;
|
|
27
|
+
organization_name: string;
|
|
28
|
+
email: string;
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
}
|
|
31
|
+
export interface MultiCustomerConfig {
|
|
32
|
+
customers: Record<string, CustomerConfig>;
|
|
33
|
+
defaultCustomer?: string | undefined;
|
|
34
|
+
}
|
|
35
|
+
export interface TokenResponse {
|
|
36
|
+
access_token?: string;
|
|
37
|
+
token?: string;
|
|
38
|
+
accessToken?: string;
|
|
39
|
+
refresh_token?: string;
|
|
40
|
+
refreshToken?: string;
|
|
41
|
+
expires_in?: number;
|
|
42
|
+
expiresIn?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface StoredTokens {
|
|
45
|
+
access_token: string;
|
|
46
|
+
refresh_token: string;
|
|
47
|
+
expires_at: number;
|
|
48
|
+
}
|
|
49
|
+
export interface ProjectMeta {
|
|
50
|
+
readonly id: string;
|
|
51
|
+
readonly idn: string;
|
|
52
|
+
readonly title: string;
|
|
53
|
+
readonly description?: string;
|
|
54
|
+
readonly created_at?: string;
|
|
55
|
+
readonly updated_at?: string;
|
|
56
|
+
}
|
|
57
|
+
export interface Agent {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly idn: string;
|
|
60
|
+
readonly title?: string;
|
|
61
|
+
readonly description?: string;
|
|
62
|
+
readonly flows?: readonly Flow[];
|
|
63
|
+
}
|
|
64
|
+
export interface Flow {
|
|
65
|
+
readonly id: string;
|
|
66
|
+
readonly idn: string;
|
|
67
|
+
readonly title: string;
|
|
68
|
+
readonly description?: string;
|
|
69
|
+
readonly default_runner_type: RunnerType;
|
|
70
|
+
readonly default_model: ModelConfig;
|
|
71
|
+
}
|
|
72
|
+
export interface ModelConfig {
|
|
73
|
+
readonly model_idn: string;
|
|
74
|
+
readonly provider_idn: string;
|
|
75
|
+
}
|
|
76
|
+
export interface SkillParameter {
|
|
77
|
+
readonly name: string;
|
|
78
|
+
readonly default_value?: string;
|
|
79
|
+
}
|
|
80
|
+
export interface Skill {
|
|
81
|
+
readonly id: string;
|
|
82
|
+
readonly idn: string;
|
|
83
|
+
readonly title: string;
|
|
84
|
+
prompt_script?: string;
|
|
85
|
+
readonly runner_type: RunnerType;
|
|
86
|
+
readonly model: ModelConfig;
|
|
87
|
+
readonly parameters: readonly SkillParameter[];
|
|
88
|
+
readonly path?: string | undefined;
|
|
89
|
+
}
|
|
90
|
+
export interface FlowEvent {
|
|
91
|
+
readonly id: string;
|
|
92
|
+
readonly idn: string;
|
|
93
|
+
readonly description: string;
|
|
94
|
+
readonly skill_selector: SkillSelector;
|
|
95
|
+
readonly skill_idn?: string;
|
|
96
|
+
readonly state_idn?: string;
|
|
97
|
+
readonly integration_idn?: string;
|
|
98
|
+
readonly connector_idn?: string;
|
|
99
|
+
readonly interrupt_mode: InterruptMode;
|
|
100
|
+
}
|
|
101
|
+
export interface FlowState {
|
|
102
|
+
readonly id: string;
|
|
103
|
+
readonly idn: string;
|
|
104
|
+
readonly title: string;
|
|
105
|
+
readonly default_value?: string;
|
|
106
|
+
readonly scope: StateFieldScope;
|
|
107
|
+
}
|
|
108
|
+
export type RunnerType = 'guidance' | 'nsl';
|
|
109
|
+
export type SkillSelector = 'first' | 'last' | 'random' | 'all';
|
|
110
|
+
export type InterruptMode = 'allow' | 'deny' | 'queue';
|
|
111
|
+
export type StateFieldScope = 'flow' | 'agent' | 'project' | 'global';
|
|
112
|
+
export interface SkillMetadata {
|
|
113
|
+
id: string;
|
|
114
|
+
title: string;
|
|
115
|
+
idn: string;
|
|
116
|
+
runner_type: RunnerType;
|
|
117
|
+
model: ModelConfig;
|
|
118
|
+
parameters: SkillParameter[];
|
|
119
|
+
path?: string | undefined;
|
|
120
|
+
}
|
|
121
|
+
export interface FlowData {
|
|
122
|
+
id: string;
|
|
123
|
+
skills: Record<string, SkillMetadata>;
|
|
124
|
+
}
|
|
125
|
+
export interface AgentData {
|
|
126
|
+
id: string;
|
|
127
|
+
flows: Record<string, FlowData>;
|
|
128
|
+
}
|
|
129
|
+
export interface ProjectData {
|
|
130
|
+
projectId: string;
|
|
131
|
+
projectIdn: string;
|
|
132
|
+
agents: Record<string, AgentData>;
|
|
133
|
+
}
|
|
134
|
+
export interface ProjectMap {
|
|
135
|
+
projects: Record<string, ProjectData>;
|
|
136
|
+
}
|
|
137
|
+
export interface LegacyProjectMap extends ProjectData {
|
|
138
|
+
projects?: Record<string, ProjectData>;
|
|
139
|
+
}
|
|
140
|
+
export interface HashStore {
|
|
141
|
+
[filePath: string]: string;
|
|
142
|
+
}
|
|
143
|
+
export interface ParsedArticle {
|
|
144
|
+
readonly topic_name: string;
|
|
145
|
+
readonly persona_id: string | null;
|
|
146
|
+
readonly topic_summary: string;
|
|
147
|
+
readonly topic_facts: readonly string[];
|
|
148
|
+
readonly confidence: number;
|
|
149
|
+
readonly source: string;
|
|
150
|
+
readonly labels: readonly string[];
|
|
151
|
+
}
|
|
152
|
+
export interface AkbImportArticle extends Omit<ParsedArticle, 'persona_id'> {
|
|
153
|
+
persona_id: string;
|
|
154
|
+
}
|
|
155
|
+
export interface CliArgs {
|
|
156
|
+
readonly _: readonly string[];
|
|
157
|
+
readonly verbose?: boolean;
|
|
158
|
+
readonly v?: boolean;
|
|
159
|
+
readonly [key: string]: unknown;
|
|
160
|
+
}
|
|
161
|
+
export interface FlowsYamlSkill {
|
|
162
|
+
idn: string;
|
|
163
|
+
title: string;
|
|
164
|
+
prompt_script: string;
|
|
165
|
+
runner_type: string;
|
|
166
|
+
model: ModelConfig;
|
|
167
|
+
parameters: Array<{
|
|
168
|
+
name: string;
|
|
169
|
+
default_value: string;
|
|
170
|
+
}>;
|
|
171
|
+
}
|
|
172
|
+
export interface FlowsYamlEvent {
|
|
173
|
+
title: string;
|
|
174
|
+
idn: string;
|
|
175
|
+
skill_selector: string;
|
|
176
|
+
skill_idn?: string | undefined;
|
|
177
|
+
state_idn?: string | undefined;
|
|
178
|
+
integration_idn?: string | undefined;
|
|
179
|
+
connector_idn?: string | undefined;
|
|
180
|
+
interrupt_mode: string;
|
|
181
|
+
}
|
|
182
|
+
export interface FlowsYamlState {
|
|
183
|
+
title: string;
|
|
184
|
+
idn: string;
|
|
185
|
+
default_value?: string | undefined;
|
|
186
|
+
scope: string;
|
|
187
|
+
}
|
|
188
|
+
export interface FlowsYamlFlow {
|
|
189
|
+
idn: string;
|
|
190
|
+
title: string;
|
|
191
|
+
description: string | null;
|
|
192
|
+
default_runner_type: string;
|
|
193
|
+
default_provider_idn: string;
|
|
194
|
+
default_model_idn: string;
|
|
195
|
+
skills: FlowsYamlSkill[];
|
|
196
|
+
events: FlowsYamlEvent[];
|
|
197
|
+
state_fields: FlowsYamlState[];
|
|
198
|
+
}
|
|
199
|
+
export interface FlowsYamlAgent {
|
|
200
|
+
agent_idn: string;
|
|
201
|
+
agent_description?: string | undefined;
|
|
202
|
+
agent_flows: FlowsYamlFlow[];
|
|
203
|
+
}
|
|
204
|
+
export interface FlowsYamlData {
|
|
205
|
+
flows: FlowsYamlAgent[];
|
|
206
|
+
}
|
|
207
|
+
export interface AxiosClientConfig {
|
|
208
|
+
baseURL?: string;
|
|
209
|
+
headers?: Record<string, string>;
|
|
210
|
+
}
|
|
211
|
+
export interface NewoApiError extends Error {
|
|
212
|
+
response?: {
|
|
213
|
+
status: number;
|
|
214
|
+
data: unknown;
|
|
215
|
+
};
|
|
216
|
+
config?: {
|
|
217
|
+
method?: string;
|
|
218
|
+
url?: string;
|
|
219
|
+
headers?: Record<string, string>;
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
export type FileStatus = 'M' | 'D' | 'clean';
|
|
223
|
+
export interface StatusResult {
|
|
224
|
+
filePath: string;
|
|
225
|
+
status: FileStatus;
|
|
226
|
+
oldHash?: string;
|
|
227
|
+
newHash?: string;
|
|
228
|
+
}
|
|
229
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "NEWO CLI: sync flows/skills between NEWO and local files, multi-project support, import AKB articles",
|
|
5
|
-
"type": "module",
|
|
6
5
|
"bin": {
|
|
7
|
-
"newo": "
|
|
6
|
+
"newo": "dist/cli.js"
|
|
8
7
|
},
|
|
9
8
|
"files": [
|
|
10
|
-
"
|
|
9
|
+
"dist/**/*.js",
|
|
10
|
+
"dist/**/*.d.ts",
|
|
11
|
+
"src/**/*.ts",
|
|
11
12
|
"README.md",
|
|
12
13
|
"CHANGELOG.md",
|
|
13
14
|
".env.example"
|
|
@@ -44,19 +45,39 @@
|
|
|
44
45
|
"dotenv": "^16.4.5",
|
|
45
46
|
"fs-extra": "^11.2.0",
|
|
46
47
|
"js-yaml": "^4.1.0",
|
|
47
|
-
"minimist": "^1.2.8"
|
|
48
|
+
"minimist": "^1.2.8",
|
|
49
|
+
"p-limit": "^5.0.0"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
50
|
-
"
|
|
52
|
+
"@types/chai": "^4.3.11",
|
|
53
|
+
"@types/fs-extra": "^11.0.4",
|
|
54
|
+
"@types/js-yaml": "^4.0.9",
|
|
55
|
+
"@types/minimist": "^1.2.5",
|
|
56
|
+
"@types/node": "^22.5.4",
|
|
57
|
+
"@types/sinon": "^17.0.3",
|
|
58
|
+
"c8": "^9.1.0",
|
|
59
|
+
"chai": "^5.0.2",
|
|
60
|
+
"mocha": "^10.2.0",
|
|
61
|
+
"sinon": "^18.0.1",
|
|
62
|
+
"tsx": "^4.20.5",
|
|
63
|
+
"typescript": "^5.6.2"
|
|
51
64
|
},
|
|
52
65
|
"scripts": {
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
66
|
+
"build": "tsc",
|
|
67
|
+
"build:watch": "tsc --watch",
|
|
68
|
+
"dev": "npm run build && node ./dist/cli.js",
|
|
69
|
+
"dev:watch": "tsc --watch & nodemon --watch dist dist/cli.js",
|
|
70
|
+
"pull": "npm run build && node ./dist/cli.js pull",
|
|
71
|
+
"push": "npm run build && node ./dist/cli.js push",
|
|
72
|
+
"status": "npm run build && node ./dist/cli.js status",
|
|
73
|
+
"clean": "rm -rf dist coverage",
|
|
74
|
+
"typecheck": "tsc --noEmit",
|
|
75
|
+
"lint": "tsc --noEmit --strict",
|
|
76
|
+
"test": "npm run build && node --test test/*.test.js",
|
|
77
|
+
"test:unit": "npm run build && node --test test/{api,sync,auth,hash,fsutil,akb}.test.js",
|
|
78
|
+
"test:integration": "npm run build && node --test test/integration.test.js",
|
|
79
|
+
"test:coverage": "npm run build && c8 --reporter=html --reporter=text node --test test/*.test.js",
|
|
80
|
+
"test:mocha": "npm run build && c8 mocha test/*.test.js --timeout 60000",
|
|
81
|
+
"prepublishOnly": "npm run clean && npm run build"
|
|
61
82
|
}
|
|
62
83
|
}
|