newo 1.5.0 → 1.5.2

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/dist/env.js ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Environment validation errors with clear messaging
3
+ */
4
+ export class EnvValidationError extends Error {
5
+ constructor(message) {
6
+ super(`Environment validation failed: ${message}`);
7
+ this.name = 'EnvValidationError';
8
+ }
9
+ }
10
+ /**
11
+ * Validates required environment variables and returns typed configuration
12
+ */
13
+ export function validateEnvironment() {
14
+ const env = process.env;
15
+ const baseUrl = env.NEWO_BASE_URL?.trim() || 'https://app.newo.ai';
16
+ const projectId = env.NEWO_PROJECT_ID?.trim();
17
+ const apiKey = env.NEWO_API_KEY?.trim();
18
+ const accessToken = env.NEWO_ACCESS_TOKEN?.trim();
19
+ const refreshToken = env.NEWO_REFRESH_TOKEN?.trim();
20
+ const refreshUrl = env.NEWO_REFRESH_URL?.trim();
21
+ // Base URL validation
22
+ if (!isValidUrl(baseUrl)) {
23
+ throw new EnvValidationError(`NEWO_BASE_URL must be a valid URL. Received: ${baseUrl}`);
24
+ }
25
+ // Project ID is optional - if not set, pull all projects
26
+ // If provided, validate UUID format
27
+ if (projectId && !isValidUuid(projectId)) {
28
+ throw new EnvValidationError(`NEWO_PROJECT_ID must be a valid UUID when provided. Received: ${projectId}`);
29
+ }
30
+ // Authentication validation - at least one method required
31
+ const hasApiKey = !!apiKey;
32
+ const hasApiKeys = !!env.NEWO_API_KEYS?.trim();
33
+ const hasDirectTokens = !!(accessToken && refreshToken);
34
+ if (!hasApiKey && !hasApiKeys && !hasDirectTokens) {
35
+ throw new EnvValidationError('Authentication required: Set NEWO_API_KEY, NEWO_API_KEYS (recommended), or both NEWO_ACCESS_TOKEN and NEWO_REFRESH_TOKEN');
36
+ }
37
+ // If refresh URL is provided, validate it
38
+ if (refreshUrl && !isValidUrl(refreshUrl)) {
39
+ throw new EnvValidationError(`NEWO_REFRESH_URL must be a valid URL when provided. Received: ${refreshUrl}`);
40
+ }
41
+ return {
42
+ NEWO_BASE_URL: baseUrl,
43
+ NEWO_PROJECT_ID: projectId || undefined,
44
+ NEWO_API_KEY: apiKey,
45
+ NEWO_API_KEYS: env.NEWO_API_KEYS?.trim(),
46
+ NEWO_ACCESS_TOKEN: accessToken,
47
+ NEWO_REFRESH_TOKEN: refreshToken,
48
+ NEWO_REFRESH_URL: refreshUrl,
49
+ NEWO_DEFAULT_CUSTOMER: env.NEWO_DEFAULT_CUSTOMER?.trim(),
50
+ };
51
+ }
52
+ /**
53
+ * Validates if a string is a valid URL
54
+ */
55
+ function isValidUrl(urlString) {
56
+ try {
57
+ new URL(urlString);
58
+ return true;
59
+ }
60
+ catch {
61
+ return false;
62
+ }
63
+ }
64
+ /**
65
+ * Validates if a string is a valid UUID (v4 format)
66
+ */
67
+ function isValidUuid(uuid) {
68
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
69
+ return uuidRegex.test(uuid);
70
+ }
71
+ /**
72
+ * Global validated environment - call validateEnvironment() once at startup
73
+ */
74
+ export let ENV;
75
+ /**
76
+ * Initialize environment validation - must be called at application startup
77
+ */
78
+ export function initializeEnvironment() {
79
+ ENV = validateEnvironment();
80
+ return ENV;
81
+ }
82
+ //# sourceMappingURL=env.js.map
package/dist/fsutil.d.ts CHANGED
@@ -1,12 +1,20 @@
1
1
  import type { RunnerType } from './types.js';
2
- export declare const ROOT_DIR: string;
2
+ export declare const NEWO_CUSTOMERS_DIR: string;
3
3
  export declare const STATE_DIR: string;
4
+ export declare function customerDir(customerIdn: string): string;
5
+ export declare function customerProjectsDir(customerIdn: string): string;
6
+ export declare function customerStateDir(customerIdn: string): string;
7
+ export declare function mapPath(customerIdn: string): string;
8
+ export declare function hashesPath(customerIdn: string): string;
9
+ export declare function ensureState(customerIdn: string): Promise<void>;
10
+ export declare function projectDir(customerIdn: string, projectIdn: string): string;
11
+ export declare function flowsYamlPath(customerIdn: string): string;
12
+ export declare function skillPath(customerIdn: string, projectIdn: string, agentIdn: string, flowIdn: string, skillIdn: string, runnerType?: RunnerType): string;
13
+ export declare function metadataPath(customerIdn: string, projectIdn: string): string;
14
+ export declare const ROOT_DIR: string;
4
15
  export declare const MAP_PATH: string;
5
16
  export declare const HASHES_PATH: string;
6
- export declare function ensureState(): Promise<void>;
7
- export declare function projectDir(projectIdn: string): string;
8
- export declare function skillPath(projectIdn: string, agentIdn: string, flowIdn: string, skillIdn: string, runnerType?: RunnerType): string;
9
- export declare function metadataPath(projectIdn: string): string;
10
- export declare function writeFileAtomic(filepath: string, content: string): Promise<void>;
17
+ export declare function writeFileSafe(filepath: string, content: string): Promise<void>;
18
+ export declare const writeFileAtomic: typeof writeFileSafe;
11
19
  export declare function readIfExists(filepath: string): Promise<string | null>;
12
20
  //# sourceMappingURL=fsutil.d.ts.map
package/dist/fsutil.js CHANGED
@@ -1,27 +1,50 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
- export const ROOT_DIR = path.join(process.cwd(), 'projects');
3
+ export const NEWO_CUSTOMERS_DIR = path.posix.join(process.cwd(), 'newo_customers');
4
4
  export const STATE_DIR = path.join(process.cwd(), '.newo');
5
- export const MAP_PATH = path.join(STATE_DIR, 'map.json');
6
- export const HASHES_PATH = path.join(STATE_DIR, 'hashes.json');
7
- export async function ensureState() {
5
+ export function customerDir(customerIdn) {
6
+ return path.posix.join(NEWO_CUSTOMERS_DIR, customerIdn);
7
+ }
8
+ export function customerProjectsDir(customerIdn) {
9
+ return path.posix.join(customerDir(customerIdn), 'projects');
10
+ }
11
+ export function customerStateDir(customerIdn) {
12
+ return path.join(STATE_DIR, customerIdn);
13
+ }
14
+ export function mapPath(customerIdn) {
15
+ return path.join(customerStateDir(customerIdn), 'map.json');
16
+ }
17
+ export function hashesPath(customerIdn) {
18
+ return path.join(customerStateDir(customerIdn), 'hashes.json');
19
+ }
20
+ export async function ensureState(customerIdn) {
8
21
  await fs.ensureDir(STATE_DIR);
9
- await fs.ensureDir(ROOT_DIR);
22
+ await fs.ensureDir(customerStateDir(customerIdn));
23
+ await fs.ensureDir(customerProjectsDir(customerIdn));
24
+ }
25
+ export function projectDir(customerIdn, projectIdn) {
26
+ return path.posix.join(customerProjectsDir(customerIdn), projectIdn);
10
27
  }
11
- export function projectDir(projectIdn) {
12
- return path.join(ROOT_DIR, projectIdn);
28
+ export function flowsYamlPath(customerIdn) {
29
+ return path.posix.join(customerProjectsDir(customerIdn), 'flows.yaml');
13
30
  }
14
- export function skillPath(projectIdn, agentIdn, flowIdn, skillIdn, runnerType = 'guidance') {
31
+ export function skillPath(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn, runnerType = 'guidance') {
15
32
  const extension = runnerType === 'nsl' ? '.jinja' : '.guidance';
16
- return path.join(ROOT_DIR, projectIdn, agentIdn, flowIdn, `${skillIdn}${extension}`);
33
+ return path.posix.join(customerProjectsDir(customerIdn), projectIdn, agentIdn, flowIdn, `${skillIdn}${extension}`);
17
34
  }
18
- export function metadataPath(projectIdn) {
19
- return path.join(ROOT_DIR, projectIdn, 'metadata.json');
35
+ export function metadataPath(customerIdn, projectIdn) {
36
+ return path.posix.join(customerProjectsDir(customerIdn), projectIdn, 'metadata.json');
20
37
  }
21
- export async function writeFileAtomic(filepath, content) {
38
+ // Legacy support - will be deprecated
39
+ export const ROOT_DIR = path.posix.join(process.cwd(), 'projects');
40
+ export const MAP_PATH = path.join(STATE_DIR, 'map.json');
41
+ export const HASHES_PATH = path.join(STATE_DIR, 'hashes.json');
42
+ export async function writeFileSafe(filepath, content) {
22
43
  await fs.ensureDir(path.dirname(filepath));
23
44
  await fs.writeFile(filepath, content, 'utf8');
24
45
  }
46
+ // Deprecated: use writeFileSafe instead
47
+ export const writeFileAtomic = writeFileSafe;
25
48
  export async function readIfExists(filepath) {
26
49
  return (await fs.pathExists(filepath)) ? fs.readFile(filepath, 'utf8') : null;
27
50
  }
package/dist/hash.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { HashStore } from './types.js';
2
2
  export declare function sha256(str: string): string;
3
- export declare function loadHashes(): Promise<HashStore>;
4
- export declare function saveHashes(hashes: HashStore): Promise<void>;
3
+ export declare function loadHashes(customerIdn?: string): Promise<HashStore>;
4
+ export declare function saveHashes(hashes: HashStore, customerIdn?: string): Promise<void>;
5
5
  //# sourceMappingURL=hash.d.ts.map
package/dist/hash.js CHANGED
@@ -1,17 +1,40 @@
1
1
  import crypto from 'crypto';
2
2
  import fs from 'fs-extra';
3
- import { ensureState, HASHES_PATH } from './fsutil.js';
3
+ import { ensureState, hashesPath, HASHES_PATH } from './fsutil.js';
4
4
  export function sha256(str) {
5
5
  return crypto.createHash('sha256').update(str, 'utf8').digest('hex');
6
6
  }
7
- export async function loadHashes() {
8
- await ensureState();
9
- if (await fs.pathExists(HASHES_PATH)) {
10
- return fs.readJson(HASHES_PATH);
7
+ export async function loadHashes(customerIdn) {
8
+ if (customerIdn) {
9
+ await ensureState(customerIdn);
10
+ try {
11
+ return await fs.readJson(hashesPath(customerIdn));
12
+ }
13
+ catch (error) {
14
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
15
+ return {};
16
+ }
17
+ throw error;
18
+ }
19
+ }
20
+ // Legacy support
21
+ try {
22
+ return await fs.readJson(HASHES_PATH);
23
+ }
24
+ catch (error) {
25
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
26
+ return {};
27
+ }
28
+ throw error;
11
29
  }
12
- return {};
13
30
  }
14
- export async function saveHashes(hashes) {
15
- await fs.writeJson(HASHES_PATH, hashes, { spaces: 2 });
31
+ export async function saveHashes(hashes, customerIdn) {
32
+ if (customerIdn) {
33
+ await fs.writeJson(hashesPath(customerIdn), hashes, { spaces: 2 });
34
+ }
35
+ else {
36
+ // Legacy support
37
+ await fs.writeJson(HASHES_PATH, hashes, { spaces: 2 });
38
+ }
16
39
  }
17
40
  //# sourceMappingURL=hash.js.map
package/dist/sync.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { AxiosInstance } from 'axios';
2
- import type { ProjectData } from './types.js';
3
- export declare function pullSingleProject(client: AxiosInstance, projectId: string, projectIdn: string, verbose?: boolean): Promise<ProjectData>;
4
- export declare function pullAll(client: AxiosInstance, projectId?: string | null, verbose?: boolean): Promise<void>;
5
- export declare function pushChanged(client: AxiosInstance, verbose?: boolean): Promise<void>;
6
- export declare function status(verbose?: boolean): Promise<void>;
2
+ import type { ProjectData, CustomerConfig } from './types.js';
3
+ export declare function pullSingleProject(client: AxiosInstance, customer: CustomerConfig, projectId: string, projectIdn: string, verbose?: boolean): Promise<ProjectData>;
4
+ export declare function pullAll(client: AxiosInstance, customer: CustomerConfig, projectId?: string | null, verbose?: boolean): Promise<void>;
5
+ export declare function pushChanged(client: AxiosInstance, customer: CustomerConfig, verbose?: boolean): Promise<void>;
6
+ export declare function status(customer: CustomerConfig, verbose?: boolean): Promise<void>;
7
7
  //# sourceMappingURL=sync.d.ts.map
package/dist/sync.js CHANGED
@@ -1,18 +1,27 @@
1
1
  import { listProjects, listAgents, listFlowSkills, updateSkill, listFlowEvents, listFlowStates, getProjectMeta } from './api.js';
2
- import { ensureState, skillPath, writeFileAtomic, readIfExists, MAP_PATH, projectDir, metadataPath } from './fsutil.js';
2
+ import { ensureState, skillPath, writeFileSafe, readIfExists, mapPath, metadataPath, flowsYamlPath } from './fsutil.js';
3
3
  import fs from 'fs-extra';
4
4
  import { sha256, loadHashes, saveHashes } from './hash.js';
5
5
  import yaml from 'js-yaml';
6
- import path from 'path';
7
- export async function pullSingleProject(client, projectId, projectIdn, verbose = false) {
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) {
8
17
  if (verbose)
9
- console.log(`🔍 Fetching agents for project ${projectId} (${projectIdn})...`);
18
+ console.log(`🔍 Fetching agents for project ${projectId} (${projectIdn}) for customer ${customer.idn}...`);
10
19
  const agents = await listAgents(client, projectId);
11
20
  if (verbose)
12
21
  console.log(`📦 Found ${agents.length} agents`);
13
22
  // Get and save project metadata
14
23
  const projectMeta = await getProjectMeta(client, projectId);
15
- await writeFileAtomic(metadataPath(projectIdn), JSON.stringify(projectMeta, null, 2));
24
+ await writeFileSafe(metadataPath(customer.idn, projectIdn), JSON.stringify(projectMeta, null, 2));
16
25
  if (verbose)
17
26
  console.log(`✓ Saved metadata for ${projectIdn}`);
18
27
  const projectMap = { projectId, projectIdn, agents: {} };
@@ -22,9 +31,10 @@ export async function pullSingleProject(client, projectId, projectIdn, verbose =
22
31
  for (const flow of agent.flows ?? []) {
23
32
  projectMap.agents[aKey].flows[flow.idn] = { id: flow.id, skills: {} };
24
33
  const skills = await listFlowSkills(client, flow.id);
25
- for (const skill of skills) {
26
- const file = skillPath(projectIdn, agent.idn, flow.idn, skill.idn, skill.runner_type);
27
- await writeFileAtomic(file, skill.prompt_script || '');
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 || '');
28
38
  // Store complete skill metadata for push operations
29
39
  projectMap.agents[aKey].flows[flow.idn].skills[skill.idn] = {
30
40
  id: skill.id,
@@ -32,44 +42,44 @@ export async function pullSingleProject(client, projectId, projectIdn, verbose =
32
42
  idn: skill.idn,
33
43
  runner_type: skill.runner_type,
34
44
  model: skill.model,
35
- parameters: skill.parameters,
45
+ parameters: [...skill.parameters],
36
46
  path: skill.path || undefined
37
47
  };
38
48
  console.log(`✓ Pulled ${file}`);
39
- }
49
+ })));
40
50
  }
41
51
  }
42
52
  // Generate flows.yaml for this project
43
53
  if (verbose)
44
- console.log(`📄 Generating flows.yaml for ${projectIdn}...`);
45
- await generateFlowsYaml(client, agents, projectIdn, verbose);
54
+ console.log(`📄 Generating flows.yaml...`);
55
+ await generateFlowsYaml(client, customer, agents, verbose);
46
56
  return projectMap;
47
57
  }
48
- export async function pullAll(client, projectId = null, verbose = false) {
49
- await ensureState();
58
+ export async function pullAll(client, customer, projectId = null, verbose = false) {
59
+ await ensureState(customer.idn);
50
60
  if (projectId) {
51
61
  // Single project mode
52
62
  const projectMeta = await getProjectMeta(client, projectId);
53
- const projectMap = await pullSingleProject(client, projectId, projectMeta.idn, verbose);
63
+ const projectMap = await pullSingleProject(client, customer, projectId, projectMeta.idn, verbose);
54
64
  const idMap = { projects: { [projectMeta.idn]: projectMap } };
55
- await fs.writeJson(MAP_PATH, idMap, { spaces: 2 });
65
+ await fs.writeJson(mapPath(customer.idn), idMap, { spaces: 2 });
56
66
  // Generate hash tracking for this project
57
67
  const hashes = {};
58
68
  for (const [agentIdn, agentObj] of Object.entries(projectMap.agents)) {
59
69
  for (const [flowIdn, flowObj] of Object.entries(agentObj.flows)) {
60
70
  for (const [skillIdn, skillMeta] of Object.entries(flowObj.skills)) {
61
- const p = skillPath(projectMeta.idn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
71
+ const p = skillPath(customer.idn, projectMeta.idn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
62
72
  const content = await fs.readFile(p, 'utf8');
63
73
  hashes[p] = sha256(content);
64
74
  }
65
75
  }
66
76
  }
67
- await saveHashes(hashes);
77
+ await saveHashes(hashes, customer.idn);
68
78
  return;
69
79
  }
70
80
  // Multi-project mode
71
81
  if (verbose)
72
- console.log('🔍 Fetching all projects...');
82
+ console.log(`🔍 Fetching all projects for customer ${customer.idn}...`);
73
83
  const projects = await listProjects(client);
74
84
  if (verbose)
75
85
  console.log(`📦 Found ${projects.length} projects`);
@@ -78,40 +88,44 @@ export async function pullAll(client, projectId = null, verbose = false) {
78
88
  for (const project of projects) {
79
89
  if (verbose)
80
90
  console.log(`\n📁 Processing project: ${project.idn} (${project.title})`);
81
- const projectMap = await pullSingleProject(client, project.id, project.idn, verbose);
91
+ const projectMap = await pullSingleProject(client, customer, project.id, project.idn, verbose);
82
92
  idMap.projects[project.idn] = projectMap;
83
93
  // Collect hashes for this project
84
94
  for (const [agentIdn, agentObj] of Object.entries(projectMap.agents)) {
85
95
  for (const [flowIdn, flowObj] of Object.entries(agentObj.flows)) {
86
96
  for (const [skillIdn, skillMeta] of Object.entries(flowObj.skills)) {
87
- const p = skillPath(project.idn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
97
+ const p = skillPath(customer.idn, project.idn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
88
98
  const content = await fs.readFile(p, 'utf8');
89
99
  allHashes[p] = sha256(content);
90
100
  }
91
101
  }
92
102
  }
93
103
  }
94
- await fs.writeJson(MAP_PATH, idMap, { spaces: 2 });
95
- await saveHashes(allHashes);
104
+ await fs.writeJson(mapPath(customer.idn), idMap, { spaces: 2 });
105
+ await saveHashes(allHashes, customer.idn);
96
106
  }
97
- export async function pushChanged(client, verbose = false) {
98
- await ensureState();
99
- if (!(await fs.pathExists(MAP_PATH))) {
100
- throw new Error('Missing .newo/map.json. Run `newo pull` first.');
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.`);
101
111
  }
102
112
  if (verbose)
103
- console.log('📋 Loading project mapping...');
104
- const idMap = await fs.readJson(MAP_PATH);
113
+ console.log(`📋 Loading project mapping for customer ${customer.idn}...`);
114
+ const idMapData = await fs.readJson(mapPath(customer.idn));
105
115
  if (verbose)
106
116
  console.log('🔍 Loading file hashes...');
107
- const oldHashes = await loadHashes();
117
+ const oldHashes = await loadHashes(customer.idn);
108
118
  const newHashes = { ...oldHashes };
109
119
  if (verbose)
110
120
  console.log('🔄 Scanning for changes...');
111
121
  let pushed = 0;
112
122
  let scanned = 0;
113
- // Handle both old single-project format and new multi-project format
114
- const projects = 'projects' in idMap && idMap.projects ? idMap.projects : { '': idMap };
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'); })();
115
129
  for (const [projectIdn, projectData] of Object.entries(projects)) {
116
130
  if (verbose && projectIdn)
117
131
  console.log(`📁 Scanning project: ${projectIdn}`);
@@ -123,8 +137,8 @@ export async function pushChanged(client, verbose = false) {
123
137
  console.log(` 📁 Scanning flow: ${flowIdn}`);
124
138
  for (const [skillIdn, skillMeta] of Object.entries(flowObj.skills)) {
125
139
  const p = projectIdn ?
126
- skillPath(projectIdn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type) :
127
- skillPath('', agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
140
+ skillPath(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type) :
141
+ skillPath(customer.idn, '', agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
128
142
  scanned++;
129
143
  if (verbose)
130
144
  console.log(` 📄 Checking: ${p}`);
@@ -177,22 +191,26 @@ export async function pushChanged(client, verbose = false) {
177
191
  }
178
192
  if (verbose)
179
193
  console.log(`🔄 Scanned ${scanned} files, found ${pushed} changes`);
180
- await saveHashes(newHashes);
194
+ await saveHashes(newHashes, customer.idn);
181
195
  console.log(pushed ? `✅ Push complete. ${pushed} file(s) updated.` : '✅ Nothing to push.');
182
196
  }
183
- export async function status(verbose = false) {
184
- await ensureState();
185
- if (!(await fs.pathExists(MAP_PATH))) {
186
- console.log('No map. Run `newo pull` first.');
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.`);
187
201
  return;
188
202
  }
189
203
  if (verbose)
190
- console.log('📋 Loading project mapping and hashes...');
191
- const idMap = await fs.readJson(MAP_PATH);
192
- const hashes = await loadHashes();
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);
193
207
  let dirty = 0;
194
- // Handle both old single-project format and new multi-project format
195
- const projects = 'projects' in idMap && idMap.projects ? idMap.projects : { '': idMap };
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'); })();
196
214
  for (const [projectIdn, projectData] of Object.entries(projects)) {
197
215
  if (verbose && projectIdn)
198
216
  console.log(`📁 Checking project: ${projectIdn}`);
@@ -204,8 +222,8 @@ export async function status(verbose = false) {
204
222
  console.log(` 📁 Checking flow: ${flowIdn}`);
205
223
  for (const [skillIdn, skillMeta] of Object.entries(flowObj.skills)) {
206
224
  const p = projectIdn ?
207
- skillPath(projectIdn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type) :
208
- skillPath('', agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
225
+ skillPath(customer.idn, projectIdn, agentIdn, flowIdn, skillIdn, skillMeta.runner_type) :
226
+ skillPath(customer.idn, '', agentIdn, flowIdn, skillIdn, skillMeta.runner_type);
209
227
  const exists = await fs.pathExists(p);
210
228
  if (!exists) {
211
229
  console.log(`D ${p}`);
@@ -237,21 +255,38 @@ export async function status(verbose = false) {
237
255
  }
238
256
  console.log(dirty ? `${dirty} changed file(s).` : 'Clean.');
239
257
  }
240
- async function generateFlowsYaml(client, agents, projectIdn, verbose = false) {
258
+ async function generateFlowsYaml(client, customer, agents, verbose = false) {
241
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
+ }
242
266
  for (const agent of agents) {
243
267
  if (verbose)
244
268
  console.log(` 📁 Processing agent: ${agent.idn}`);
245
269
  const agentFlows = [];
246
270
  for (const flow of agent.flows ?? []) {
247
- if (verbose)
271
+ processedFlows++;
272
+ if (verbose) {
248
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
+ }
249
284
  // Get skills for this flow
250
285
  const skills = await listFlowSkills(client, flow.id);
251
286
  const skillsData = skills.map(skill => ({
252
287
  idn: skill.idn,
253
288
  title: skill.title || "",
254
- prompt_script: `flows/${flow.idn}/${skill.idn}.${skill.runner_type === 'nsl' ? 'jinja' : 'nsl'}`,
289
+ prompt_script: `flows/${flow.idn}/${skill.idn}.${skill.runner_type === 'nsl' ? 'jinja' : 'guidance'}`,
255
290
  runner_type: `!enum "RunnerType.${skill.runner_type}"`,
256
291
  model: {
257
292
  model_idn: skill.model.model_idn,
@@ -319,6 +354,10 @@ async function generateFlowsYaml(client, agents, projectIdn, verbose = false) {
319
354
  };
320
355
  flowsData.flows.push(agentData);
321
356
  }
357
+ // Clear progress bar and move to new line
358
+ if (!verbose && totalFlows > 0) {
359
+ process.stdout.write('\n');
360
+ }
322
361
  // Convert to YAML and write to file with custom enum handling
323
362
  let yamlContent = yaml.dump(flowsData, {
324
363
  indent: 2,
@@ -330,8 +369,8 @@ async function generateFlowsYaml(client, agents, projectIdn, verbose = false) {
330
369
  });
331
370
  // Post-process to fix enum formatting
332
371
  yamlContent = yamlContent.replace(/"(!enum "[^"]+")"/g, '$1');
333
- const yamlPath = path.join(projectDir(projectIdn), 'flows.yaml');
334
- await writeFileAtomic(yamlPath, yamlContent);
335
- console.log(`✓ Generated flows.yaml for ${projectIdn}`);
372
+ const yamlPath = flowsYamlPath(customer.idn);
373
+ await writeFileSafe(yamlPath, yamlContent);
374
+ console.log(`✓ Generated flows.yaml`);
336
375
  }
337
376
  //# sourceMappingURL=sync.js.map