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/src/types.ts ADDED
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Comprehensive type definitions for NEWO CLI
3
+ */
4
+
5
+ export interface NewoEnvironment {
6
+ NEWO_BASE_URL?: string;
7
+ NEWO_PROJECT_ID?: string;
8
+ NEWO_API_KEY?: string;
9
+ NEWO_API_KEYS?: string; // JSON string containing array of keys or key objects
10
+ NEWO_ACCESS_TOKEN?: string;
11
+ NEWO_REFRESH_TOKEN?: string;
12
+ NEWO_REFRESH_URL?: string;
13
+ NEWO_DEFAULT_CUSTOMER?: string;
14
+ // Dynamic customer entries will be detected at runtime
15
+ [key: string]: string | undefined;
16
+ }
17
+
18
+ export interface ApiKeyConfig {
19
+ key: string;
20
+ project_id?: string;
21
+ }
22
+
23
+ export interface CustomerConfig {
24
+ idn: string;
25
+ apiKey: string;
26
+ projectId?: string | undefined;
27
+ }
28
+
29
+ export interface CustomerProfile {
30
+ id: string;
31
+ idn: string;
32
+ organization_name: string;
33
+ email: string;
34
+ [key: string]: any;
35
+ }
36
+
37
+ export interface MultiCustomerConfig {
38
+ customers: Record<string, CustomerConfig>;
39
+ defaultCustomer?: string | undefined;
40
+ }
41
+
42
+ // Authentication Types
43
+ export interface TokenResponse {
44
+ access_token?: string;
45
+ token?: string;
46
+ accessToken?: string;
47
+ refresh_token?: string;
48
+ refreshToken?: string;
49
+ expires_in?: number;
50
+ expiresIn?: number;
51
+ }
52
+
53
+ export interface StoredTokens {
54
+ access_token: string;
55
+ refresh_token: string;
56
+ expires_at: number;
57
+ }
58
+
59
+ // API Response Types
60
+ export interface ProjectMeta {
61
+ readonly id: string;
62
+ readonly idn: string;
63
+ readonly title: string;
64
+ readonly description?: string;
65
+ readonly created_at?: string;
66
+ readonly updated_at?: string;
67
+ }
68
+
69
+ export interface Agent {
70
+ readonly id: string;
71
+ readonly idn: string;
72
+ readonly title?: string;
73
+ readonly description?: string;
74
+ readonly flows?: readonly Flow[];
75
+ }
76
+
77
+ export interface Flow {
78
+ readonly id: string;
79
+ readonly idn: string;
80
+ readonly title: string;
81
+ readonly description?: string;
82
+ readonly default_runner_type: RunnerType;
83
+ readonly default_model: ModelConfig;
84
+ }
85
+
86
+ export interface ModelConfig {
87
+ readonly model_idn: string;
88
+ readonly provider_idn: string;
89
+ }
90
+
91
+ export interface SkillParameter {
92
+ readonly name: string;
93
+ readonly default_value?: string;
94
+ }
95
+
96
+ export interface Skill {
97
+ readonly id: string;
98
+ readonly idn: string;
99
+ readonly title: string;
100
+ prompt_script?: string; // Mutable for updates
101
+ readonly runner_type: RunnerType;
102
+ readonly model: ModelConfig;
103
+ readonly parameters: readonly SkillParameter[];
104
+ readonly path?: string | undefined;
105
+ }
106
+
107
+ export interface FlowEvent {
108
+ readonly id: string;
109
+ readonly idn: string;
110
+ readonly description: string;
111
+ readonly skill_selector: SkillSelector;
112
+ readonly skill_idn?: string;
113
+ readonly state_idn?: string;
114
+ readonly integration_idn?: string;
115
+ readonly connector_idn?: string;
116
+ readonly interrupt_mode: InterruptMode;
117
+ }
118
+
119
+ export interface FlowState {
120
+ readonly id: string;
121
+ readonly idn: string;
122
+ readonly title: string;
123
+ readonly default_value?: string;
124
+ readonly scope: StateFieldScope;
125
+ }
126
+
127
+ // Enum Types
128
+ export type RunnerType = 'guidance' | 'nsl';
129
+ export type SkillSelector = 'first' | 'last' | 'random' | 'all';
130
+ export type InterruptMode = 'allow' | 'deny' | 'queue';
131
+ export type StateFieldScope = 'flow' | 'agent' | 'project' | 'global';
132
+
133
+ // File System Types
134
+ export interface SkillMetadata {
135
+ id: string;
136
+ title: string;
137
+ idn: string;
138
+ runner_type: RunnerType;
139
+ model: ModelConfig;
140
+ parameters: SkillParameter[];
141
+ path?: string | undefined;
142
+ }
143
+
144
+ export interface FlowData {
145
+ id: string;
146
+ skills: Record<string, SkillMetadata>;
147
+ }
148
+
149
+ export interface AgentData {
150
+ id: string;
151
+ flows: Record<string, FlowData>;
152
+ }
153
+
154
+ export interface ProjectData {
155
+ projectId: string;
156
+ projectIdn: string;
157
+ agents: Record<string, AgentData>;
158
+ }
159
+
160
+ export interface ProjectMap {
161
+ projects: Record<string, ProjectData>;
162
+ }
163
+
164
+ // Legacy single-project format support
165
+ export interface LegacyProjectMap extends ProjectData {
166
+ projects?: Record<string, ProjectData>;
167
+ }
168
+
169
+ export interface HashStore {
170
+ [filePath: string]: string;
171
+ }
172
+
173
+ // AKB Types
174
+ export interface ParsedArticle {
175
+ readonly topic_name: string;
176
+ readonly persona_id: string | null;
177
+ readonly topic_summary: string;
178
+ readonly topic_facts: readonly string[];
179
+ readonly confidence: number;
180
+ readonly source: string;
181
+ readonly labels: readonly string[];
182
+ }
183
+
184
+ export interface AkbImportArticle extends Omit<ParsedArticle, 'persona_id'> {
185
+ persona_id: string;
186
+ }
187
+
188
+ // CLI Types
189
+ export interface CliArgs {
190
+ readonly _: readonly string[];
191
+ readonly verbose?: boolean;
192
+ readonly v?: boolean;
193
+ readonly [key: string]: unknown;
194
+ }
195
+
196
+ // flows.yaml Generation Types
197
+ export interface FlowsYamlSkill {
198
+ idn: string;
199
+ title: string;
200
+ prompt_script: string;
201
+ runner_type: string;
202
+ model: ModelConfig;
203
+ parameters: Array<{
204
+ name: string;
205
+ default_value: string;
206
+ }>;
207
+ }
208
+
209
+ export interface FlowsYamlEvent {
210
+ title: string;
211
+ idn: string;
212
+ skill_selector: string;
213
+ skill_idn?: string | undefined;
214
+ state_idn?: string | undefined;
215
+ integration_idn?: string | undefined;
216
+ connector_idn?: string | undefined;
217
+ interrupt_mode: string;
218
+ }
219
+
220
+ export interface FlowsYamlState {
221
+ title: string;
222
+ idn: string;
223
+ default_value?: string | undefined;
224
+ scope: string;
225
+ }
226
+
227
+ export interface FlowsYamlFlow {
228
+ idn: string;
229
+ title: string;
230
+ description: string | null;
231
+ default_runner_type: string;
232
+ default_provider_idn: string;
233
+ default_model_idn: string;
234
+ skills: FlowsYamlSkill[];
235
+ events: FlowsYamlEvent[];
236
+ state_fields: FlowsYamlState[];
237
+ }
238
+
239
+ export interface FlowsYamlAgent {
240
+ agent_idn: string;
241
+ agent_description?: string | undefined;
242
+ agent_flows: FlowsYamlFlow[];
243
+ }
244
+
245
+ export interface FlowsYamlData {
246
+ flows: FlowsYamlAgent[];
247
+ }
248
+
249
+ // HTTP Client Types
250
+ export interface AxiosClientConfig {
251
+ baseURL?: string;
252
+ headers?: Record<string, string>;
253
+ }
254
+
255
+ // Error Types
256
+ export interface NewoApiError extends Error {
257
+ response?: {
258
+ status: number;
259
+ data: unknown;
260
+ };
261
+ config?: {
262
+ method?: string;
263
+ url?: string;
264
+ headers?: Record<string, string>;
265
+ };
266
+ }
267
+
268
+ // Status Types
269
+ export type FileStatus = 'M' | 'D' | 'clean';
270
+
271
+ export interface StatusResult {
272
+ filePath: string;
273
+ status: FileStatus;
274
+ oldHash?: string;
275
+ newHash?: string;
276
+ }
package/src/api.js DELETED
@@ -1,103 +0,0 @@
1
- import axios from 'axios';
2
- import dotenv from 'dotenv';
3
- import { getValidAccessToken, forceReauth } from './auth.js';
4
- dotenv.config();
5
-
6
- const { NEWO_BASE_URL } = process.env;
7
-
8
- export async function makeClient(verbose = false) {
9
- let accessToken = await getValidAccessToken();
10
- if (verbose) console.log('✓ Access token obtained');
11
-
12
- const client = axios.create({
13
- baseURL: NEWO_BASE_URL,
14
- headers: { accept: 'application/json' }
15
- });
16
-
17
- client.interceptors.request.use(async (config) => {
18
- config.headers = config.headers || {};
19
- config.headers.Authorization = `Bearer ${accessToken}`;
20
- if (verbose) {
21
- console.log(`→ ${config.method?.toUpperCase()} ${config.url}`);
22
- if (config.data) console.log(' Data:', JSON.stringify(config.data, null, 2));
23
- if (config.params) console.log(' Params:', config.params);
24
- }
25
- return config;
26
- });
27
-
28
- let retried = false;
29
- client.interceptors.response.use(
30
- r => {
31
- if (verbose) {
32
- console.log(`← ${r.status} ${r.config.method?.toUpperCase()} ${r.config.url}`);
33
- if (r.data && Object.keys(r.data).length < 20) {
34
- console.log(' Response:', JSON.stringify(r.data, null, 2));
35
- } else if (r.data) {
36
- console.log(` Response: [${typeof r.data}] ${Array.isArray(r.data) ? r.data.length + ' items' : 'large object'}`);
37
- }
38
- }
39
- return r;
40
- },
41
- async (error) => {
42
- const status = error?.response?.status;
43
- if (verbose) {
44
- console.log(`← ${status} ${error.config?.method?.toUpperCase()} ${error.config?.url} - ${error.message}`);
45
- if (error.response?.data) console.log(' Error data:', error.response.data);
46
- }
47
- if (status === 401 && !retried) {
48
- retried = true;
49
- if (verbose) console.log('🔄 Retrying with fresh token...');
50
- accessToken = await forceReauth();
51
- error.config.headers.Authorization = `Bearer ${accessToken}`;
52
- return client.request(error.config);
53
- }
54
- throw error;
55
- }
56
- );
57
-
58
- return client;
59
- }
60
-
61
- export async function listProjects(client) {
62
- const r = await client.get(`/api/v1/designer/projects`);
63
- return r.data;
64
- }
65
-
66
- export async function listAgents(client, projectId) {
67
- const r = await client.get(`/api/v1/bff/agents/list`, { params: { project_id: projectId } });
68
- return r.data;
69
- }
70
-
71
- export async function getProjectMeta(client, projectId) {
72
- const r = await client.get(`/api/v1/designer/projects/by-id/${projectId}`);
73
- return r.data;
74
- }
75
-
76
- export async function listFlowSkills(client, flowId) {
77
- const r = await client.get(`/api/v1/designer/flows/${flowId}/skills`);
78
- return r.data;
79
- }
80
-
81
- export async function getSkill(client, skillId) {
82
- const r = await client.get(`/api/v1/designer/skills/${skillId}`);
83
- return r.data;
84
- }
85
-
86
- export async function updateSkill(client, skillObject) {
87
- await client.put(`/api/v1/designer/flows/skills/${skillObject.id}`, skillObject);
88
- }
89
-
90
- export async function listFlowEvents(client, flowId) {
91
- const r = await client.get(`/api/v1/designer/flows/${flowId}/events`);
92
- return r.data;
93
- }
94
-
95
- export async function listFlowStates(client, flowId) {
96
- const r = await client.get(`/api/v1/designer/flows/${flowId}/states`);
97
- return r.data;
98
- }
99
-
100
- export async function importAkbArticle(client, articleData) {
101
- const r = await client.post('/api/v1/akb/append-manual', articleData);
102
- return r.data;
103
- }
package/src/auth.js DELETED
@@ -1,92 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import axios from 'axios';
4
- import dotenv from 'dotenv';
5
- dotenv.config();
6
-
7
- const {
8
- NEWO_BASE_URL,
9
- NEWO_API_KEY,
10
- NEWO_ACCESS_TOKEN,
11
- NEWO_REFRESH_TOKEN,
12
- NEWO_REFRESH_URL
13
- } = process.env;
14
-
15
- const STATE_DIR = path.join(process.cwd(), '.newo');
16
- const TOKENS_PATH = path.join(STATE_DIR, 'tokens.json');
17
-
18
- async function saveTokens(tokens) {
19
- await fs.ensureDir(STATE_DIR);
20
- await fs.writeJson(TOKENS_PATH, tokens, { spaces: 2 });
21
- }
22
-
23
- async function loadTokens() {
24
- if (await fs.pathExists(TOKENS_PATH)) {
25
- return fs.readJson(TOKENS_PATH);
26
- }
27
- if (NEWO_ACCESS_TOKEN || NEWO_REFRESH_TOKEN) {
28
- const t = {
29
- access_token: NEWO_ACCESS_TOKEN || '',
30
- refresh_token: NEWO_REFRESH_TOKEN || '',
31
- expires_at: Date.now() + 10 * 60 * 1000
32
- };
33
- await saveTokens(t);
34
- return t;
35
- }
36
- return null;
37
- }
38
-
39
- function isExpired(tokens) {
40
- if (!tokens?.expires_at) return false;
41
- return Date.now() >= tokens.expires_at - 10_000;
42
- }
43
-
44
- export async function exchangeApiKeyForToken() {
45
- if (!NEWO_API_KEY) throw new Error('NEWO_API_KEY not set. Provide an API key in .env');
46
- const url = `${NEWO_BASE_URL}/api/v1/auth/api-key/token`;
47
- const res = await axios.post(url, {}, { headers: { 'x-api-key': NEWO_API_KEY, 'accept': 'application/json' } });
48
- const data = res.data || {};
49
- const access = data.access_token || data.token || data.accessToken;
50
- const refresh = data.refresh_token || data.refreshToken || '';
51
- const expiresInSec = data.expires_in || data.expiresIn || 3600;
52
- const tokens = { access_token: access, refresh_token: refresh, expires_at: Date.now() + expiresInSec * 1000 };
53
- await saveTokens(tokens);
54
- return tokens;
55
- }
56
-
57
- export async function refreshWithEndpoint(refreshToken) {
58
- if (!NEWO_REFRESH_URL) throw new Error('NEWO_REFRESH_URL not set');
59
- const res = await axios.post(NEWO_REFRESH_URL, { refresh_token: refreshToken }, { headers: { 'accept': 'application/json' } });
60
- const data = res.data || {};
61
- const access = data.access_token || data.token || data.accessToken;
62
- const refresh = data.refresh_token ?? refreshToken;
63
- const expiresInSec = data.expires_in || 3600;
64
- const tokens = { access_token: access, refresh_token: refresh, expires_at: Date.now() + expiresInSec * 1000 };
65
- await saveTokens(tokens);
66
- return tokens;
67
- }
68
-
69
- export async function getValidAccessToken() {
70
- let tokens = await loadTokens();
71
- if (!tokens || !tokens.access_token) {
72
- tokens = await exchangeApiKeyForToken();
73
- return tokens.access_token;
74
- }
75
- if (!isExpired(tokens)) return tokens.access_token;
76
-
77
- if (NEWO_REFRESH_URL && tokens.refresh_token) {
78
- try {
79
- tokens = await refreshWithEndpoint(tokens.refresh_token);
80
- return tokens.access_token;
81
- } catch (e) {
82
- console.warn('Refresh failed, falling back to API key exchange…');
83
- }
84
- }
85
- tokens = await exchangeApiKeyForToken();
86
- return tokens.access_token;
87
- }
88
-
89
- export async function forceReauth() {
90
- const tokens = await exchangeApiKeyForToken();
91
- return tokens.access_token;
92
- }
package/src/cli.js DELETED
@@ -1,108 +0,0 @@
1
- #!/usr/bin/env node
2
- import minimist from 'minimist';
3
- import dotenv from 'dotenv';
4
- import { makeClient, getProjectMeta, importAkbArticle } from './api.js';
5
- import { pullAll, pushChanged, status } from './sync.js';
6
- import { parseAkbFile, prepareArticlesForImport } from './akb.js';
7
- import path from 'path';
8
-
9
- dotenv.config();
10
- const { NEWO_PROJECT_ID } = process.env;
11
-
12
- async function main() {
13
- const args = minimist(process.argv.slice(2));
14
- const cmd = args._[0];
15
- const verbose = args.verbose || args.v;
16
-
17
- if (!cmd || ['help', '-h', '--help'].includes(cmd)) {
18
- console.log(`NEWO CLI
19
- Usage:
20
- newo pull # download all projects -> ./projects/ OR specific project if NEWO_PROJECT_ID set
21
- newo push # upload modified *.guidance/*.jinja back to NEWO
22
- newo status # show modified files
23
- newo meta # get project metadata (debug, requires NEWO_PROJECT_ID)
24
- newo import-akb <file> <persona_id> # import AKB articles from file
25
-
26
- Flags:
27
- --verbose, -v # enable detailed logging
28
-
29
- Env:
30
- NEWO_BASE_URL, NEWO_PROJECT_ID (optional), NEWO_API_KEY, NEWO_REFRESH_URL (optional)
31
-
32
- Notes:
33
- - multi-project support: pull downloads all accessible projects or single project based on NEWO_PROJECT_ID
34
- - If NEWO_PROJECT_ID is set, pull downloads only that project
35
- - If NEWO_PROJECT_ID is not set, pull downloads all projects accessible with your API key
36
- - Projects are stored in ./projects/{project-idn}/ folders
37
- - Each project folder contains metadata.json and flows.yaml
38
- `);
39
- return;
40
- }
41
-
42
- const client = await makeClient(verbose);
43
-
44
- if (cmd === 'pull') {
45
- // If PROJECT_ID is set, pull single project; otherwise pull all projects
46
- await pullAll(client, NEWO_PROJECT_ID || null, verbose);
47
- } else if (cmd === 'push') {
48
- await pushChanged(client, verbose);
49
- } else if (cmd === 'status') {
50
- await status(verbose);
51
- } else if (cmd === 'meta') {
52
- if (!NEWO_PROJECT_ID) throw new Error('NEWO_PROJECT_ID is not set in env');
53
- const meta = await getProjectMeta(client, NEWO_PROJECT_ID);
54
- console.log(JSON.stringify(meta, null, 2));
55
- } else if (cmd === 'import-akb') {
56
- const akbFile = args._[1];
57
- const personaId = args._[2];
58
-
59
- if (!akbFile || !personaId) {
60
- console.error('Usage: newo import-akb <file> <persona_id>');
61
- console.error('Example: newo import-akb akb.txt da4550db-2b95-4500-91ff-fb4b60fe7be9');
62
- process.exit(1);
63
- }
64
-
65
- const filePath = path.resolve(akbFile);
66
-
67
- try {
68
- if (verbose) console.log(`📖 Parsing AKB file: ${filePath}`);
69
- const articles = parseAkbFile(filePath);
70
- console.log(`✓ Parsed ${articles.length} articles from ${akbFile}`);
71
-
72
- if (verbose) console.log(`🔧 Preparing articles for persona: ${personaId}`);
73
- const preparedArticles = prepareArticlesForImport(articles, personaId);
74
-
75
- let successCount = 0;
76
- let errorCount = 0;
77
-
78
- console.log(`📤 Importing ${preparedArticles.length} articles...`);
79
-
80
- for (const [index, article] of preparedArticles.entries()) {
81
- try {
82
- if (verbose) console.log(` [${index + 1}/${preparedArticles.length}] Importing ${article.topic_name}...`);
83
- await importAkbArticle(client, article);
84
- successCount++;
85
- if (!verbose) process.stdout.write('.');
86
- } catch (error) {
87
- errorCount++;
88
- console.error(`\n❌ Failed to import ${article.topic_name}:`, error?.response?.data || error.message);
89
- }
90
- }
91
-
92
- if (!verbose) console.log(''); // new line after dots
93
- console.log(`✅ Import complete: ${successCount} successful, ${errorCount} failed`);
94
-
95
- } catch (error) {
96
- console.error('❌ AKB import failed:', error.message);
97
- process.exit(1);
98
- }
99
- } else {
100
- console.error('Unknown command:', cmd);
101
- process.exit(1);
102
- }
103
- }
104
-
105
- main().catch((e) => {
106
- console.error(e?.response?.data || e);
107
- process.exit(1);
108
- });
package/src/fsutil.js DELETED
@@ -1,34 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
-
4
- export const ROOT_DIR = path.join(process.cwd(), 'projects');
5
- export const STATE_DIR = path.join(process.cwd(), '.newo');
6
- export const MAP_PATH = path.join(STATE_DIR, 'map.json');
7
- export const HASHES_PATH = path.join(STATE_DIR, 'hashes.json');
8
-
9
- export async function ensureState() {
10
- await fs.ensureDir(STATE_DIR);
11
- await fs.ensureDir(ROOT_DIR);
12
- }
13
-
14
- export function projectDir(projectIdn) {
15
- return path.join(ROOT_DIR, projectIdn);
16
- }
17
-
18
- export function skillPath(projectIdn, agentIdn, flowIdn, skillIdn, runnerType = 'guidance') {
19
- const extension = runnerType === 'nsl' ? '.jinja' : '.guidance';
20
- return path.join(ROOT_DIR, projectIdn, agentIdn, flowIdn, `${skillIdn}${extension}`);
21
- }
22
-
23
- export function metadataPath(projectIdn) {
24
- return path.join(ROOT_DIR, projectIdn, 'metadata.json');
25
- }
26
-
27
- export async function writeFileAtomic(filepath, content) {
28
- await fs.ensureDir(path.dirname(filepath));
29
- await fs.writeFile(filepath, content, 'utf8');
30
- }
31
-
32
- export async function readIfExists(filepath) {
33
- return (await fs.pathExists(filepath)) ? fs.readFile(filepath, 'utf8') : null;
34
- }
package/src/hash.js DELETED
@@ -1,17 +0,0 @@
1
- import crypto from 'crypto';
2
- import fs from 'fs-extra';
3
- import { ensureState, HASHES_PATH } from './fsutil.js';
4
-
5
- export function sha256(str) {
6
- return crypto.createHash('sha256').update(str, 'utf8').digest('hex');
7
- }
8
-
9
- export async function loadHashes() {
10
- await ensureState();
11
- if (await fs.pathExists(HASHES_PATH)) return fs.readJson(HASHES_PATH);
12
- return {};
13
- }
14
-
15
- export async function saveHashes(h) {
16
- await fs.writeJson(HASHES_PATH, h, { spaces: 2 });
17
- }