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/.env.example +17 -6
- package/CHANGELOG.md +91 -0
- package/README.md +502 -105
- package/dist/akb.d.ts +1 -1
- package/dist/akb.js +21 -17
- package/dist/api.d.ts +3 -2
- package/dist/api.js +24 -21
- package/dist/auth.d.ts +5 -5
- package/dist/auth.js +332 -75
- package/dist/cli.js +225 -29
- 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 +14 -6
- package/dist/fsutil.js +35 -12
- package/dist/hash.d.ts +2 -2
- package/dist/hash.js +31 -8
- package/dist/sync.d.ts +5 -5
- package/dist/sync.js +91 -52
- package/dist/types.d.ts +76 -53
- package/package.json +16 -9
- package/src/akb.ts +23 -18
- package/src/api.ts +27 -24
- package/src/auth.ts +367 -94
- package/src/cli.ts +234 -33
- 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 +43 -11
- package/src/hash.ts +29 -8
- package/src/sync.ts +105 -54
- package/src/types.ts +82 -54
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
|
|
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
|
|
7
|
-
export declare
|
|
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
|
|
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
|
|
6
|
-
|
|
7
|
-
|
|
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(
|
|
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
|
|
12
|
-
return path.join(
|
|
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(
|
|
33
|
+
return path.posix.join(customerProjectsDir(customerIdn), projectIdn, agentIdn, flowIdn, `${skillIdn}${extension}`);
|
|
17
34
|
}
|
|
18
|
-
export function metadataPath(projectIdn) {
|
|
19
|
-
return path.join(
|
|
35
|
+
export function metadataPath(customerIdn, projectIdn) {
|
|
36
|
+
return path.posix.join(customerProjectsDir(customerIdn), projectIdn, 'metadata.json');
|
|
20
37
|
}
|
|
21
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
45
|
-
await generateFlowsYaml(client,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
100
|
-
throw new Error(
|
|
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(
|
|
104
|
-
const
|
|
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 =
|
|
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(
|
|
186
|
-
console.log(
|
|
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(
|
|
191
|
-
const
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
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' : '
|
|
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 =
|
|
334
|
-
await
|
|
335
|
-
console.log(`✓ Generated flows.yaml
|
|
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
|