newo 1.9.2 → 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 +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 +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 -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 +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 -1418
  66. package/src/types.ts +0 -1
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Status checking module
3
+ */
4
+ import fs from 'fs-extra';
5
+ import yaml from 'js-yaml';
6
+ import { sha256, loadHashes } from '../hash.js';
7
+ import { ensureState, mapPath, skillMetadataPath, customerAttributesPath, customerAttributesBackupPath, flowsYamlPath } from '../fsutil.js';
8
+ import { validateSkillFolder, getSingleSkillFile } from './skill-files.js';
9
+ // Type guards for project map formats
10
+ function isProjectMap(x) {
11
+ return typeof x === 'object' && x !== null && 'projects' in x;
12
+ }
13
+ function isLegacyProjectMap(x) {
14
+ return typeof x === 'object' && x !== null && 'projectId' in x && 'agents' in x;
15
+ }
16
+ /**
17
+ * Check status of files for a customer
18
+ */
19
+ export async function status(customer, verbose = false) {
20
+ await ensureState(customer.idn);
21
+ if (!(await fs.pathExists(mapPath(customer.idn)))) {
22
+ console.log(`No map for customer ${customer.idn}. Run \`newo pull --customer ${customer.idn}\` first.`);
23
+ return;
24
+ }
25
+ if (verbose)
26
+ console.log(`📋 Loading project mapping and hashes for customer ${customer.idn}...`);
27
+ const idMapData = await fs.readJson(mapPath(customer.idn));
28
+ const hashes = await loadHashes(customer.idn);
29
+ let dirty = 0;
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] of Object.entries(flowObj.skills)) {
46
+ // Validate skill folder and show warnings
47
+ const validation = await validateSkillFolder(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn);
48
+ if (!validation.isValid) {
49
+ // Show warnings and errors
50
+ validation.errors.forEach(error => {
51
+ console.error(`❌ ${error}`);
52
+ });
53
+ validation.warnings.forEach(warning => {
54
+ console.warn(`⚠️ ${warning}`);
55
+ });
56
+ if (validation.files.length > 1) {
57
+ console.warn(`⚠️ Multiple script files in skill ${skillIdn}:`);
58
+ validation.files.forEach(file => {
59
+ console.warn(` • ${file.fileName}`);
60
+ });
61
+ console.warn(` Status check skipped - please keep only one script file.`);
62
+ }
63
+ else if (validation.files.length === 0) {
64
+ console.log(`D ${skillIdn}/ (no script files)`);
65
+ dirty++;
66
+ }
67
+ continue;
68
+ }
69
+ // Get the single valid script file
70
+ const skillFile = await getSingleSkillFile(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn);
71
+ if (!skillFile) {
72
+ console.log(`D ${skillIdn}/ (no valid script file)`);
73
+ dirty++;
74
+ if (verbose)
75
+ console.log(` ❌ No valid script file found: ${skillIdn}`);
76
+ continue;
77
+ }
78
+ const content = skillFile.content;
79
+ const currentPath = skillFile.filePath;
80
+ const h = sha256(content);
81
+ const oldHash = hashes[currentPath];
82
+ if (verbose) {
83
+ console.log(` 📄 ${currentPath}`);
84
+ console.log(` Old hash: ${oldHash || 'none'}`);
85
+ console.log(` New hash: ${h}`);
86
+ }
87
+ if (oldHash !== h) {
88
+ console.log(`M ${currentPath}`);
89
+ dirty++;
90
+ if (verbose)
91
+ console.log(` 🔄 Modified: ${skillFile.fileName}`);
92
+ }
93
+ else if (verbose) {
94
+ console.log(` ✓ Unchanged: ${skillFile.fileName}`);
95
+ }
96
+ }
97
+ // Check metadata.yaml files for changes
98
+ for (const [skillIdn] of Object.entries(flowObj.skills)) {
99
+ const metadataPath = projectIdn ?
100
+ skillMetadataPath(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn) :
101
+ skillMetadataPath(customer.idn, '', agentIdn, flowIdn, skillIdn);
102
+ if (await fs.pathExists(metadataPath)) {
103
+ const metadataContent = await fs.readFile(metadataPath, 'utf8');
104
+ const h = sha256(metadataContent);
105
+ const oldHash = hashes[metadataPath];
106
+ if (verbose) {
107
+ console.log(` 📄 ${metadataPath}`);
108
+ console.log(` Old hash: ${oldHash || 'none'}`);
109
+ console.log(` New hash: ${h}`);
110
+ }
111
+ if (oldHash !== h) {
112
+ console.log(`M ${metadataPath}`);
113
+ dirty++;
114
+ // Show which metadata fields changed
115
+ try {
116
+ const newMetadata = yaml.load(metadataContent);
117
+ console.log(` 📊 Metadata changed for skill: ${skillIdn}`);
118
+ if (newMetadata?.title) {
119
+ console.log(` • Title: ${newMetadata.title}`);
120
+ }
121
+ if (newMetadata?.runner_type) {
122
+ console.log(` • Runner: ${newMetadata.runner_type}`);
123
+ }
124
+ if (newMetadata?.model) {
125
+ console.log(` • Model: ${newMetadata.model.provider_idn}/${newMetadata.model.model_idn}`);
126
+ }
127
+ }
128
+ catch (e) {
129
+ if (verbose)
130
+ console.log(` 🔄 Modified: metadata.yaml`);
131
+ }
132
+ }
133
+ else if (verbose) {
134
+ console.log(` ✓ Unchanged: ${metadataPath}`);
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+ }
141
+ // Check attributes file for changes
142
+ try {
143
+ const attributesFile = customerAttributesPath(customer.idn);
144
+ if (await fs.pathExists(attributesFile)) {
145
+ const content = await fs.readFile(attributesFile, 'utf8');
146
+ const h = sha256(content);
147
+ const oldHash = hashes[attributesFile];
148
+ if (verbose) {
149
+ console.log(`📄 ${attributesFile}`);
150
+ console.log(` Old hash: ${oldHash || 'none'}`);
151
+ console.log(` New hash: ${h}`);
152
+ }
153
+ if (oldHash !== h) {
154
+ console.log(`M ${attributesFile}`);
155
+ dirty++;
156
+ // Show which attributes changed by comparing with backup
157
+ try {
158
+ const attributesBackupFile = customerAttributesBackupPath(customer.idn);
159
+ if (await fs.pathExists(attributesBackupFile)) {
160
+ const backupContent = await fs.readFile(attributesBackupFile, 'utf8');
161
+ const parseYaml = (content) => {
162
+ let yamlContent = content.replace(/!enum "([^"]+)"/g, '"$1"');
163
+ return yaml.load(yamlContent);
164
+ };
165
+ const currentData = parseYaml(content);
166
+ const backupData = parseYaml(backupContent);
167
+ if (currentData?.attributes && backupData?.attributes) {
168
+ const currentAttrs = new Map(currentData.attributes.map(attr => [attr.idn, attr]));
169
+ const backupAttrs = new Map(backupData.attributes.map(attr => [attr.idn, attr]));
170
+ const changedAttributes = [];
171
+ for (const [idn, currentAttr] of currentAttrs) {
172
+ const backupAttr = backupAttrs.get(idn);
173
+ const hasChanged = !backupAttr ||
174
+ currentAttr.value !== backupAttr.value ||
175
+ currentAttr.title !== backupAttr.title ||
176
+ currentAttr.description !== backupAttr.description ||
177
+ currentAttr.group !== backupAttr.group ||
178
+ currentAttr.is_hidden !== backupAttr.is_hidden;
179
+ if (hasChanged) {
180
+ changedAttributes.push(idn);
181
+ }
182
+ }
183
+ if (changedAttributes.length > 0) {
184
+ console.log(` 📊 Changed attributes (${changedAttributes.length}):`);
185
+ changedAttributes.slice(0, 5).forEach(idn => {
186
+ const current = currentAttrs.get(idn);
187
+ console.log(` • ${idn}: ${current?.title || 'No title'}`);
188
+ });
189
+ if (changedAttributes.length > 5) {
190
+ console.log(` ... and ${changedAttributes.length - 5} more`);
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ catch (e) {
197
+ // Fallback to simple message if diff analysis fails
198
+ }
199
+ if (verbose)
200
+ console.log(` 🔄 Modified: attributes.yaml`);
201
+ }
202
+ else if (verbose) {
203
+ console.log(` ✓ Unchanged: attributes.yaml`);
204
+ }
205
+ }
206
+ }
207
+ catch (error) {
208
+ if (verbose)
209
+ console.log(`⚠️ Error checking attributes: ${error instanceof Error ? error.message : String(error)}`);
210
+ }
211
+ // Check flows.yaml file for changes
212
+ const flowsFile = flowsYamlPath(customer.idn);
213
+ if (await fs.pathExists(flowsFile)) {
214
+ try {
215
+ const flowsContent = await fs.readFile(flowsFile, 'utf8');
216
+ const h = sha256(flowsContent);
217
+ const oldHash = hashes[flowsFile];
218
+ if (verbose) {
219
+ console.log(`📄 flows.yaml`);
220
+ console.log(` Old hash: ${oldHash || 'none'}`);
221
+ console.log(` New hash: ${h}`);
222
+ }
223
+ if (oldHash !== h) {
224
+ console.log(`M ${flowsFile}`);
225
+ dirty++;
226
+ if (verbose) {
227
+ const flowsStats = await fs.stat(flowsFile);
228
+ console.log(` 🔄 Modified: flows.yaml`);
229
+ console.log(` 📊 Size: ${(flowsStats.size / 1024).toFixed(1)}KB`);
230
+ console.log(` 📅 Last modified: ${flowsStats.mtime.toISOString()}`);
231
+ }
232
+ }
233
+ else if (verbose) {
234
+ const flowsStats = await fs.stat(flowsFile);
235
+ console.log(` ✓ Unchanged: flows.yaml`);
236
+ console.log(` 📅 Last modified: ${flowsStats.mtime.toISOString()}`);
237
+ console.log(` 📊 Size: ${(flowsStats.size / 1024).toFixed(1)}KB`);
238
+ }
239
+ }
240
+ catch (error) {
241
+ if (verbose)
242
+ console.log(`⚠️ Error checking flows.yaml: ${error instanceof Error ? error.message : String(error)}`);
243
+ }
244
+ }
245
+ console.log(dirty ? `${dirty} changed file(s).` : 'Clean.');
246
+ }
247
+ //# sourceMappingURL=status.js.map
package/dist/sync.d.ts CHANGED
@@ -1,9 +1,11 @@
1
- import type { AxiosInstance } from 'axios';
2
- import type { ProjectData, CustomerConfig, ConversationOptions } from './types.js';
3
- export declare function saveCustomerAttributes(client: AxiosInstance, customer: CustomerConfig, verbose?: boolean): Promise<void>;
4
- export declare function pullSingleProject(client: AxiosInstance, customer: CustomerConfig, projectId: string, projectIdn: string, verbose?: boolean): Promise<ProjectData>;
5
- export declare function pullAll(client: AxiosInstance, customer: CustomerConfig, projectId?: string | null, verbose?: boolean): Promise<void>;
6
- export declare function pushChanged(client: AxiosInstance, customer: CustomerConfig, verbose?: boolean): Promise<void>;
7
- export declare function status(customer: CustomerConfig, verbose?: boolean): Promise<void>;
8
- export declare function pullConversations(client: AxiosInstance, customer: CustomerConfig, options?: ConversationOptions, verbose?: boolean): Promise<void>;
1
+ /**
2
+ * NEWO CLI Sync Operations - Modular architecture entry point
3
+ */
4
+ export { saveCustomerAttributes } from './sync/attributes.js';
5
+ export { pullConversations } from './sync/conversations.js';
6
+ export { status } from './sync/status.js';
7
+ export { pullSingleProject, pullAll } from './sync/projects.js';
8
+ export { pushChanged } from './sync/push.js';
9
+ export { generateFlowsYaml } from './sync/metadata.js';
10
+ export { isProjectMap, isLegacyProjectMap } from './sync/projects.js';
9
11
  //# sourceMappingURL=sync.d.ts.map