newo 3.4.2 → 3.6.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 +5 -0
- package/CHANGELOG.md +27 -0
- package/README.md +27 -9
- package/dist/api.d.ts +18 -0
- package/dist/api.js +28 -0
- package/dist/cli/commands/export.d.ts +3 -0
- package/dist/cli/commands/export.js +62 -0
- package/dist/cli/commands/help.js +54 -42
- package/dist/cli/commands/pull.js +38 -14
- package/dist/cli/commands/push.js +32 -32
- package/dist/cli/commands/status.js +46 -7
- package/dist/cli-new/bootstrap.d.ts +7 -1
- package/dist/cli-new/bootstrap.js +11 -5
- package/dist/cli-new/di/tokens.d.ts +1 -0
- package/dist/cli-new/di/tokens.js +1 -0
- package/dist/cli.js +4 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +5 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.js +97 -8
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.d.ts +80 -0
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.js +725 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.js +1 -0
- package/dist/format/detect.d.ts +14 -0
- package/dist/format/detect.js +105 -0
- package/dist/format/extensions.d.ts +26 -0
- package/dist/format/extensions.js +45 -0
- package/dist/format/index.d.ts +11 -0
- package/dist/format/index.js +11 -0
- package/dist/format/paths-v2.d.ts +31 -0
- package/dist/format/paths-v2.js +104 -0
- package/dist/format/types.d.ts +28 -0
- package/dist/format/types.js +21 -0
- package/dist/format/v2-yaml.d.ts +143 -0
- package/dist/format/v2-yaml.js +222 -0
- package/dist/format/yaml-patch.d.ts +14 -0
- package/dist/format/yaml-patch.js +184 -0
- package/dist/fsutil.d.ts +10 -0
- package/dist/fsutil.js +25 -0
- package/dist/sync/attributes.js +3 -3
- package/dist/sync/skill-files.js +2 -2
- package/dist/types.d.ts +5 -0
- package/package.json +1 -1
- package/src/api.ts +64 -0
- package/src/cli/commands/export.ts +78 -0
- package/src/cli/commands/help.ts +54 -42
- package/src/cli/commands/pull.ts +46 -15
- package/src/cli/commands/push.ts +38 -31
- package/src/cli/commands/status.ts +59 -9
- package/src/cli-new/bootstrap.ts +19 -7
- package/src/cli-new/di/tokens.ts +1 -0
- package/src/cli.ts +5 -0
- package/src/domain/strategies/sync/ProjectSyncStrategy.ts +122 -8
- package/src/domain/strategies/sync/V2ProjectSyncStrategy.ts +1007 -0
- package/src/env.ts +2 -0
- package/src/format/detect.ts +123 -0
- package/src/format/extensions.ts +61 -0
- package/src/format/index.ts +66 -0
- package/src/format/paths-v2.ts +207 -0
- package/src/format/types.ts +40 -0
- package/src/format/v2-yaml.ts +345 -0
- package/src/format/yaml-patch.ts +208 -0
- package/src/fsutil.ts +37 -0
- package/src/sync/attributes.ts +3 -3
- package/src/sync/skill-files.ts +2 -2
- package/src/types.ts +6 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* V2 YAML parsers and generators
|
|
3
|
+
*
|
|
4
|
+
* Handles reading/writing the newo_v2 format YAML files:
|
|
5
|
+
* - Flow YAML: {FlowIdn}.yaml (inline skill definitions, events, state_fields)
|
|
6
|
+
* - Project YAML: {project_idn}.yaml
|
|
7
|
+
* - Agent YAML: agent.yaml
|
|
8
|
+
* - Library YAML: {library_idn}.yaml
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'fs-extra';
|
|
11
|
+
import yaml from 'js-yaml';
|
|
12
|
+
import { patchYamlToPyyaml } from './yaml-patch.js';
|
|
13
|
+
// ── Custom YAML tag for !enum values ──
|
|
14
|
+
const enumTag = new yaml.Type('!enum', {
|
|
15
|
+
kind: 'scalar',
|
|
16
|
+
resolve: () => true,
|
|
17
|
+
construct: (data) => data,
|
|
18
|
+
represent: (data) => String(data),
|
|
19
|
+
});
|
|
20
|
+
const V2_YAML_SCHEMA = yaml.DEFAULT_SCHEMA.extend([enumTag]);
|
|
21
|
+
// ── Shared YAML dump options ──
|
|
22
|
+
const YAML_DUMP_OPTIONS = {
|
|
23
|
+
indent: 2,
|
|
24
|
+
quotingType: '"',
|
|
25
|
+
forceQuotes: false,
|
|
26
|
+
lineWidth: -1,
|
|
27
|
+
noRefs: true,
|
|
28
|
+
sortKeys: false,
|
|
29
|
+
flowLevel: -1,
|
|
30
|
+
schema: V2_YAML_SCHEMA,
|
|
31
|
+
};
|
|
32
|
+
// ── Skill sorting: CamelCase first, then _prefixed, then snake_case ──
|
|
33
|
+
function skillSortKey(idn) {
|
|
34
|
+
if (idn.startsWith('_')) {
|
|
35
|
+
return `1_${idn}`; // _prefixed second
|
|
36
|
+
}
|
|
37
|
+
if (idn[0] && idn[0] === idn[0].toUpperCase()) {
|
|
38
|
+
return `0_${idn}`; // CamelCase first
|
|
39
|
+
}
|
|
40
|
+
return `2_${idn}`; // snake_case last
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sort skills in V2 export order (case-sensitive ASCII sort within groups):
|
|
44
|
+
* 1. CamelCase (public) - case-sensitive alphabetically
|
|
45
|
+
* 2. _prefixed (private) - case-sensitive alphabetically
|
|
46
|
+
* 3. snake_case - case-sensitive alphabetically
|
|
47
|
+
*/
|
|
48
|
+
export function sortV2Skills(skills) {
|
|
49
|
+
return [...skills].sort((a, b) => {
|
|
50
|
+
const ka = skillSortKey(a.idn);
|
|
51
|
+
const kb = skillSortKey(b.idn);
|
|
52
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Sort parameters alphabetically by name (case-sensitive, V2 export order)
|
|
57
|
+
*/
|
|
58
|
+
export function sortV2Parameters(params) {
|
|
59
|
+
return [...params].sort((a, b) => {
|
|
60
|
+
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// ── Flow YAML ──
|
|
64
|
+
export async function parseV2FlowYaml(filePath) {
|
|
65
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
66
|
+
return yaml.load(content, { schema: V2_YAML_SCHEMA });
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Generate V2 flow YAML content from API data
|
|
70
|
+
*
|
|
71
|
+
* Produces the exact format found in the reference V2 export:
|
|
72
|
+
* title, idn, description, agent_id, skills[], events[], state_fields[],
|
|
73
|
+
* default_runner_type, default_provider_idn, default_model_idn, publication_type
|
|
74
|
+
*/
|
|
75
|
+
export function generateV2FlowYaml(flowIdn, flowTitle, flowDescription, defaultRunnerType, defaultProviderIdn, defaultModelIdn, skills, events, stateFields) {
|
|
76
|
+
// Sort skills in V2 order and sort parameters within each skill
|
|
77
|
+
const sortedSkills = sortV2Skills(skills).map(s => ({
|
|
78
|
+
...s,
|
|
79
|
+
parameters: sortV2Parameters(s.parameters),
|
|
80
|
+
}));
|
|
81
|
+
// Sort events by idn, then skill_idn, then integration_idn, then connector_idn
|
|
82
|
+
const sortedEvents = [...events].sort((a, b) => {
|
|
83
|
+
if (a.idn !== b.idn)
|
|
84
|
+
return a.idn < b.idn ? -1 : 1;
|
|
85
|
+
const as = a.skill_idn || '';
|
|
86
|
+
const bs = b.skill_idn || '';
|
|
87
|
+
if (as !== bs)
|
|
88
|
+
return as < bs ? -1 : 1;
|
|
89
|
+
const ai = a.integration_idn || '';
|
|
90
|
+
const bi = b.integration_idn || '';
|
|
91
|
+
if (ai !== bi)
|
|
92
|
+
return ai < bi ? -1 : 1;
|
|
93
|
+
const ac = a.connector_idn || '';
|
|
94
|
+
const bc = b.connector_idn || '';
|
|
95
|
+
return ac < bc ? -1 : ac > bc ? 1 : 0;
|
|
96
|
+
});
|
|
97
|
+
// Sort state_fields alphabetically by idn
|
|
98
|
+
const sortedStates = [...stateFields].sort((a, b) => a.idn < b.idn ? -1 : a.idn > b.idn ? 1 : 0);
|
|
99
|
+
const flowDef = {
|
|
100
|
+
title: flowTitle,
|
|
101
|
+
idn: flowIdn,
|
|
102
|
+
description: flowDescription ?? null,
|
|
103
|
+
agent_id: null,
|
|
104
|
+
skills: sortedSkills,
|
|
105
|
+
events: sortedEvents,
|
|
106
|
+
state_fields: sortedStates,
|
|
107
|
+
default_runner_type: defaultRunnerType,
|
|
108
|
+
default_provider_idn: defaultProviderIdn,
|
|
109
|
+
default_model_idn: defaultModelIdn,
|
|
110
|
+
publication_type: null,
|
|
111
|
+
};
|
|
112
|
+
// Flow YAML uses lineWidth: -1 (no wrapping) to keep prompt_script paths on one line
|
|
113
|
+
// Then patch to convert double-quoted JSON values to single-quoted
|
|
114
|
+
return patchYamlToPyyaml(yaml.dump(flowDef, { ...YAML_DUMP_OPTIONS, lineWidth: -1 }));
|
|
115
|
+
}
|
|
116
|
+
// ── Project YAML ──
|
|
117
|
+
export async function parseV2ProjectYaml(filePath) {
|
|
118
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
119
|
+
const parsed = yaml.load(content, { schema: V2_YAML_SCHEMA });
|
|
120
|
+
return parsed.project;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Generate V2 project YAML
|
|
124
|
+
*
|
|
125
|
+
* Format:
|
|
126
|
+
* project:
|
|
127
|
+
* idn: naf
|
|
128
|
+
* name: naf
|
|
129
|
+
* version: 4.1.0
|
|
130
|
+
* description: ""
|
|
131
|
+
* is_auto_update_enabled: true
|
|
132
|
+
* registry: production
|
|
133
|
+
* registry_item_idn: naf
|
|
134
|
+
*/
|
|
135
|
+
export function generateV2ProjectYaml(meta) {
|
|
136
|
+
return yaml.dump({ project: meta }, YAML_DUMP_OPTIONS);
|
|
137
|
+
}
|
|
138
|
+
// ── Agent YAML ──
|
|
139
|
+
export async function parseV2AgentYaml(filePath) {
|
|
140
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
141
|
+
const parsed = yaml.load(content, { schema: V2_YAML_SCHEMA });
|
|
142
|
+
return parsed.agent;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Generate V2 agent YAML
|
|
146
|
+
*
|
|
147
|
+
* Format:
|
|
148
|
+
* agent:
|
|
149
|
+
* idn: TaskManager
|
|
150
|
+
* title: TaskManager
|
|
151
|
+
* description: null
|
|
152
|
+
*
|
|
153
|
+
* V2 export preserves description exactly as provided (null stays null, "" stays "")
|
|
154
|
+
*/
|
|
155
|
+
export function generateV2AgentYaml(meta) {
|
|
156
|
+
return patchYamlToPyyaml(yaml.dump({ agent: meta }, YAML_DUMP_OPTIONS));
|
|
157
|
+
}
|
|
158
|
+
// ── Library YAML ──
|
|
159
|
+
export async function parseV2LibraryYaml(filePath) {
|
|
160
|
+
const content = await fs.readFile(filePath, 'utf8');
|
|
161
|
+
return yaml.load(content, { schema: V2_YAML_SCHEMA });
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Generate V2 library YAML
|
|
165
|
+
*
|
|
166
|
+
* Format:
|
|
167
|
+
* title: Test Library
|
|
168
|
+
* idn: testLib
|
|
169
|
+
* description: Shared utility library
|
|
170
|
+
* skills:
|
|
171
|
+
* - idn: utilSkill
|
|
172
|
+
* ...
|
|
173
|
+
*/
|
|
174
|
+
export function generateV2LibraryYaml(lib) {
|
|
175
|
+
return yaml.dump(lib, YAML_DUMP_OPTIONS);
|
|
176
|
+
}
|
|
177
|
+
// ── Conversion helpers ──
|
|
178
|
+
/**
|
|
179
|
+
* Build a V2InlineSkill entry from API skill data
|
|
180
|
+
*/
|
|
181
|
+
export function buildV2InlineSkill(skillIdn, skillTitle, runnerType, modelIdn, providerIdn, parameters, promptScriptRelPath) {
|
|
182
|
+
return {
|
|
183
|
+
title: skillTitle || '',
|
|
184
|
+
idn: skillIdn,
|
|
185
|
+
prompt_script: promptScriptRelPath,
|
|
186
|
+
runner_type: runnerType,
|
|
187
|
+
model: {
|
|
188
|
+
model_idn: modelIdn,
|
|
189
|
+
provider_idn: providerIdn,
|
|
190
|
+
},
|
|
191
|
+
parameters: parameters.map(p => ({
|
|
192
|
+
name: p.name,
|
|
193
|
+
default_value: p.default_value ?? '',
|
|
194
|
+
})),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Build a V2FlowEvent entry from API event data
|
|
199
|
+
*/
|
|
200
|
+
export function buildV2FlowEvent(eventIdn, skillSelector, skillIdn, stateIdn, integrationIdn, connectorIdn, interruptMode) {
|
|
201
|
+
return {
|
|
202
|
+
idn: eventIdn,
|
|
203
|
+
skill_selector: skillSelector,
|
|
204
|
+
skill_idn: skillIdn || null,
|
|
205
|
+
state_idn: stateIdn || null,
|
|
206
|
+
integration_idn: integrationIdn || null,
|
|
207
|
+
connector_idn: connectorIdn || null,
|
|
208
|
+
interrupt_mode: interruptMode,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Build a V2StateField entry from API state data
|
|
213
|
+
*/
|
|
214
|
+
export function buildV2StateField(stateIdn, stateTitle, defaultValue, scope) {
|
|
215
|
+
return {
|
|
216
|
+
title: stateTitle || '',
|
|
217
|
+
idn: stateIdn,
|
|
218
|
+
default_value: defaultValue ?? '',
|
|
219
|
+
scope,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=v2-yaml.js.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML Post-Processor - Patches js-yaml output to match pyyaml's formatting
|
|
3
|
+
*
|
|
4
|
+
* Replicates pyyaml's Emitter wrapping behavior:
|
|
5
|
+
* - Double-quoted: breaks with `\` when column + pending > best_width (80) at space
|
|
6
|
+
* - Plain scalar: breaks at space when column > best_width
|
|
7
|
+
* - Continuation indent: parent indent + best_indent (usually +2)
|
|
8
|
+
* - Single-quote preference for strings with brackets
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Patch full YAML document output to match pyyaml formatting
|
|
12
|
+
*/
|
|
13
|
+
export declare function patchYamlToPyyaml(yamlText: string): string;
|
|
14
|
+
//# sourceMappingURL=yaml-patch.d.ts.map
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML Post-Processor - Patches js-yaml output to match pyyaml's formatting
|
|
3
|
+
*
|
|
4
|
+
* Replicates pyyaml's Emitter wrapping behavior:
|
|
5
|
+
* - Double-quoted: breaks with `\` when column + pending > best_width (80) at space
|
|
6
|
+
* - Plain scalar: breaks at space when column > best_width
|
|
7
|
+
* - Continuation indent: parent indent + best_indent (usually +2)
|
|
8
|
+
* - Single-quote preference for strings with brackets
|
|
9
|
+
*/
|
|
10
|
+
const BEST_WIDTH = 80;
|
|
11
|
+
/**
|
|
12
|
+
* Patch full YAML document output to match pyyaml formatting
|
|
13
|
+
*/
|
|
14
|
+
export function patchYamlToPyyaml(yamlText) {
|
|
15
|
+
const lines = yamlText.split('\n');
|
|
16
|
+
const result = [];
|
|
17
|
+
let inBlockScalar = false;
|
|
18
|
+
let blockIndent = 0;
|
|
19
|
+
for (let i = 0; i < lines.length; i++) {
|
|
20
|
+
const line = lines[i];
|
|
21
|
+
// Track block scalar context (|- or >- or | or >)
|
|
22
|
+
if (inBlockScalar) {
|
|
23
|
+
const currentIndent = line.length - line.trimStart().length;
|
|
24
|
+
if (line.trim() === '' || currentIndent > blockIndent) {
|
|
25
|
+
result.push(line);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
inBlockScalar = false;
|
|
29
|
+
}
|
|
30
|
+
// Detect start of block scalar
|
|
31
|
+
if (/^\s*[\w-]+:\s+[|>]-?\s*$/.test(line)) {
|
|
32
|
+
const keyIndent = line.length - line.trimStart().length;
|
|
33
|
+
inBlockScalar = true;
|
|
34
|
+
blockIndent = keyIndent;
|
|
35
|
+
result.push(line);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
result.push(...patchLine(line));
|
|
39
|
+
}
|
|
40
|
+
return result.join('\n');
|
|
41
|
+
}
|
|
42
|
+
// Keys that must NEVER be wrapped
|
|
43
|
+
const NO_WRAP_KEYS = new Set([
|
|
44
|
+
'prompt_script', 'idn', 'runner_type',
|
|
45
|
+
'model_idn', 'provider_idn', 'skill_idn', 'state_idn',
|
|
46
|
+
'integration_idn', 'connector_idn', 'interrupt_mode',
|
|
47
|
+
'skill_selector', 'name', 'scope', 'agent_id',
|
|
48
|
+
'default_runner_type', 'default_provider_idn', 'default_model_idn',
|
|
49
|
+
'publication_type', 'is_hidden', 'is_auto_update_enabled',
|
|
50
|
+
'group', 'registry', 'registry_item_idn', 'version',
|
|
51
|
+
]);
|
|
52
|
+
function patchLine(line) {
|
|
53
|
+
if (line.trim() === '' || line.trim().startsWith('#')) {
|
|
54
|
+
return [line];
|
|
55
|
+
}
|
|
56
|
+
const kvMatch = line.match(/^(\s*(?:-\s+)?)([\w-]+):\s+(.+)$/);
|
|
57
|
+
if (!kvMatch) {
|
|
58
|
+
return [line];
|
|
59
|
+
}
|
|
60
|
+
const prefix = kvMatch[1];
|
|
61
|
+
const key = kvMatch[2];
|
|
62
|
+
const value = kvMatch[3];
|
|
63
|
+
// Single-quote fix for JSON-like values (before anything else)
|
|
64
|
+
const sqFix = tryConvertToSingleQuote(value);
|
|
65
|
+
const effectiveValue = sqFix ?? value;
|
|
66
|
+
const effectiveLine = sqFix !== null ? `${prefix}${key}: ${sqFix}` : line;
|
|
67
|
+
if (NO_WRAP_KEYS.has(key)) {
|
|
68
|
+
return [effectiveLine];
|
|
69
|
+
}
|
|
70
|
+
// Only wrap if line exceeds BEST_WIDTH
|
|
71
|
+
if (effectiveLine.length <= BEST_WIDTH) {
|
|
72
|
+
return [effectiveLine];
|
|
73
|
+
}
|
|
74
|
+
const keyPart = `${prefix}${key}: `;
|
|
75
|
+
// pyyaml continuation indent = current mapping indent + best_indent
|
|
76
|
+
// For " description: ..." indent is 2, continuation = 2 + 2 = 4 spaces
|
|
77
|
+
const keyIndent = prefix.replace(/-\s+$/, '').length;
|
|
78
|
+
const contIndent = ' '.repeat(keyIndent + 2);
|
|
79
|
+
if (effectiveValue.startsWith('"') && effectiveValue.endsWith('"')) {
|
|
80
|
+
return wrapDoubleQuoted(keyPart, effectiveValue, contIndent);
|
|
81
|
+
}
|
|
82
|
+
if (effectiveValue.startsWith("'") && effectiveValue.endsWith("'")) {
|
|
83
|
+
return [effectiveLine]; // Single-quoted: don't wrap
|
|
84
|
+
}
|
|
85
|
+
return wrapPlainScalar(keyPart, effectiveValue, contIndent);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Try to convert double-quoted string with escaped chars to single-quoted
|
|
89
|
+
*/
|
|
90
|
+
function tryConvertToSingleQuote(value) {
|
|
91
|
+
if (!value.startsWith('"') || !value.endsWith('"'))
|
|
92
|
+
return null;
|
|
93
|
+
const inner = value.slice(1, -1);
|
|
94
|
+
if (!inner.includes('\\"'))
|
|
95
|
+
return null;
|
|
96
|
+
if (!inner.includes('[') && !inner.includes('{'))
|
|
97
|
+
return null;
|
|
98
|
+
const unescaped = inner.replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
|
99
|
+
if (unescaped.includes("'")) {
|
|
100
|
+
return `'${unescaped.replace(/'/g, "''")}'`;
|
|
101
|
+
}
|
|
102
|
+
return `'${unescaped}'`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Wrap double-quoted scalar matching pyyaml's write_double_quoted algorithm:
|
|
106
|
+
* - Track column from 0
|
|
107
|
+
* - At each space, check if column + pending > best_width
|
|
108
|
+
* - If yes, emit text + `\`, newline, indent, `\ ` (escaped space for continuation)
|
|
109
|
+
*/
|
|
110
|
+
function wrapDoubleQuoted(keyPart, quotedValue, contIndent) {
|
|
111
|
+
const inner = quotedValue.slice(1, -1);
|
|
112
|
+
const result = [];
|
|
113
|
+
let column = keyPart.length + 1; // keyPart + opening "
|
|
114
|
+
let lineStart = 0;
|
|
115
|
+
let lastSpace = -1;
|
|
116
|
+
for (let i = 0; i < inner.length; i++) {
|
|
117
|
+
column++;
|
|
118
|
+
if (inner[i] === ' ') {
|
|
119
|
+
lastSpace = i;
|
|
120
|
+
}
|
|
121
|
+
// pyyaml condition: column + remaining_in_word > best_width, at a space or start >= end
|
|
122
|
+
if (column > BEST_WIDTH && lastSpace > lineStart) {
|
|
123
|
+
// Break at lastSpace
|
|
124
|
+
const chunk = inner.slice(lineStart, lastSpace);
|
|
125
|
+
if (result.length === 0) {
|
|
126
|
+
result.push(`${keyPart}"${chunk}\\`);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
result.push(`${contIndent}\\ ${chunk}\\`);
|
|
130
|
+
}
|
|
131
|
+
lineStart = lastSpace + 1; // skip the space
|
|
132
|
+
column = contIndent.length + 2 + (i - lastSpace); // contIndent + "\ " + chars after space
|
|
133
|
+
lastSpace = -1;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Remaining text
|
|
137
|
+
const remaining = inner.slice(lineStart);
|
|
138
|
+
if (result.length === 0) {
|
|
139
|
+
result.push(`${keyPart}"${remaining}"`);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
result.push(`${contIndent}\\ ${remaining}"`);
|
|
143
|
+
}
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Wrap plain scalar matching pyyaml's write_plain algorithm:
|
|
148
|
+
* - At each space, if column > best_width, break
|
|
149
|
+
* - Continuation is just indented text (no backslash)
|
|
150
|
+
*/
|
|
151
|
+
function wrapPlainScalar(keyPart, value, contIndent) {
|
|
152
|
+
const result = [];
|
|
153
|
+
let column = keyPart.length;
|
|
154
|
+
let lineStart = 0;
|
|
155
|
+
let lastSpace = -1;
|
|
156
|
+
for (let i = 0; i < value.length; i++) {
|
|
157
|
+
column++;
|
|
158
|
+
if (value[i] === ' ') {
|
|
159
|
+
// pyyaml breaks at single space when column > best_width
|
|
160
|
+
if (column > BEST_WIDTH && lastSpace >= lineStart) {
|
|
161
|
+
const chunk = value.slice(lineStart, i);
|
|
162
|
+
if (result.length === 0) {
|
|
163
|
+
result.push(`${keyPart}${chunk}`);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
result.push(`${contIndent}${chunk}`);
|
|
167
|
+
}
|
|
168
|
+
lineStart = i + 1; // skip the space
|
|
169
|
+
column = contIndent.length;
|
|
170
|
+
}
|
|
171
|
+
lastSpace = i;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Remaining
|
|
175
|
+
const remaining = value.slice(lineStart);
|
|
176
|
+
if (result.length === 0) {
|
|
177
|
+
result.push(`${keyPart}${remaining}`);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
result.push(`${contIndent}${remaining}`);
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=yaml-patch.js.map
|
package/dist/fsutil.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ export declare function customerStateDir(customerIdn: string): string;
|
|
|
7
7
|
export declare function mapPath(customerIdn: string): string;
|
|
8
8
|
export declare function hashesPath(customerIdn: string): string;
|
|
9
9
|
export declare function ensureState(customerIdn: string): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Ensure only the .newo/ state directory exists (no V1 projects/ dir)
|
|
12
|
+
* Used by newo_v2 format to avoid creating V1 artifacts
|
|
13
|
+
*/
|
|
14
|
+
export declare function ensureStateOnly(customerIdn: string): Promise<void>;
|
|
10
15
|
export declare function projectDir(customerIdn: string, projectIdn: string): string;
|
|
11
16
|
export declare function flowsYamlPath(customerIdn: string): string;
|
|
12
17
|
export declare function customerAttributesPath(customerIdn: string): string;
|
|
@@ -19,6 +24,11 @@ export declare function projectMetadataPath(customerIdn: string, projectIdn: str
|
|
|
19
24
|
export declare function agentMetadataPath(customerIdn: string, projectIdn: string, agentIdn: string): string;
|
|
20
25
|
export declare function flowMetadataPath(customerIdn: string, projectIdn: string, agentIdn: string, flowIdn: string): string;
|
|
21
26
|
export declare function skillMetadataPath(customerIdn: string, projectIdn: string, agentIdn: string, flowIdn: string, skillIdn: string): string;
|
|
27
|
+
export declare function libraryDir(customerIdn: string, projectIdn: string, libraryIdn: string): string;
|
|
28
|
+
export declare function libraryMetadataPath(customerIdn: string, projectIdn: string, libraryIdn: string): string;
|
|
29
|
+
export declare function librarySkillFolderPath(customerIdn: string, projectIdn: string, libraryIdn: string, skillIdn: string): string;
|
|
30
|
+
export declare function librarySkillScriptPath(customerIdn: string, projectIdn: string, libraryIdn: string, skillIdn: string, runnerType?: RunnerType): string;
|
|
31
|
+
export declare function librarySkillMetadataPath(customerIdn: string, projectIdn: string, libraryIdn: string, skillIdn: string): string;
|
|
22
32
|
export declare function metadataPath(customerIdn: string, projectIdn: string): string;
|
|
23
33
|
export declare const ROOT_DIR: string;
|
|
24
34
|
export declare const MAP_PATH: string;
|
package/dist/fsutil.js
CHANGED
|
@@ -22,6 +22,14 @@ export async function ensureState(customerIdn) {
|
|
|
22
22
|
await fs.ensureDir(customerStateDir(customerIdn));
|
|
23
23
|
await fs.ensureDir(customerProjectsDir(customerIdn));
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Ensure only the .newo/ state directory exists (no V1 projects/ dir)
|
|
27
|
+
* Used by newo_v2 format to avoid creating V1 artifacts
|
|
28
|
+
*/
|
|
29
|
+
export async function ensureStateOnly(customerIdn) {
|
|
30
|
+
await fs.ensureDir(STATE_DIR);
|
|
31
|
+
await fs.ensureDir(customerStateDir(customerIdn));
|
|
32
|
+
}
|
|
25
33
|
export function projectDir(customerIdn, projectIdn) {
|
|
26
34
|
return path.posix.join(customerProjectsDir(customerIdn), projectIdn);
|
|
27
35
|
}
|
|
@@ -63,6 +71,23 @@ export function flowMetadataPath(customerIdn, projectIdn, agentIdn, flowIdn) {
|
|
|
63
71
|
export function skillMetadataPath(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn) {
|
|
64
72
|
return path.posix.join(skillFolderPath(customerIdn, projectIdn, agentIdn, flowIdn, skillIdn), 'metadata.yaml');
|
|
65
73
|
}
|
|
74
|
+
// Library paths (cli_v1 format)
|
|
75
|
+
export function libraryDir(customerIdn, projectIdn, libraryIdn) {
|
|
76
|
+
return path.posix.join(customerProjectsDir(customerIdn), projectIdn, 'libraries', libraryIdn);
|
|
77
|
+
}
|
|
78
|
+
export function libraryMetadataPath(customerIdn, projectIdn, libraryIdn) {
|
|
79
|
+
return path.posix.join(libraryDir(customerIdn, projectIdn, libraryIdn), 'metadata.yaml');
|
|
80
|
+
}
|
|
81
|
+
export function librarySkillFolderPath(customerIdn, projectIdn, libraryIdn, skillIdn) {
|
|
82
|
+
return path.posix.join(libraryDir(customerIdn, projectIdn, libraryIdn), skillIdn);
|
|
83
|
+
}
|
|
84
|
+
export function librarySkillScriptPath(customerIdn, projectIdn, libraryIdn, skillIdn, runnerType = 'guidance') {
|
|
85
|
+
const extension = runnerType === 'nsl' ? '.jinja' : '.guidance';
|
|
86
|
+
return path.posix.join(librarySkillFolderPath(customerIdn, projectIdn, libraryIdn, skillIdn), `${skillIdn}${extension}`);
|
|
87
|
+
}
|
|
88
|
+
export function librarySkillMetadataPath(customerIdn, projectIdn, libraryIdn, skillIdn) {
|
|
89
|
+
return path.posix.join(librarySkillFolderPath(customerIdn, projectIdn, libraryIdn, skillIdn), 'metadata.yaml');
|
|
90
|
+
}
|
|
66
91
|
// Legacy metadata path - keep for backwards compatibility
|
|
67
92
|
export function metadataPath(customerIdn, projectIdn) {
|
|
68
93
|
return path.posix.join(customerProjectsDir(customerIdn), projectIdn, 'metadata.json');
|
package/dist/sync/attributes.js
CHANGED
|
@@ -243,9 +243,9 @@ export async function pushProjectAttributes(client, customer, projectId, project
|
|
|
243
243
|
}
|
|
244
244
|
// Value type is already parsed (we removed !enum tags above)
|
|
245
245
|
const valueType = localAttr.value_type;
|
|
246
|
-
// Check if value changed
|
|
247
|
-
const localValue = String(localAttr.value
|
|
248
|
-
const remoteValue = String(remoteAttr.value
|
|
246
|
+
// Check if value changed (use ?? to preserve 0, false, empty string)
|
|
247
|
+
const localValue = String(localAttr.value ?? '');
|
|
248
|
+
const remoteValue = String(remoteAttr.value ?? '');
|
|
249
249
|
if (localValue !== remoteValue) {
|
|
250
250
|
if (verbose)
|
|
251
251
|
console.log(` 🔄 Updating project attribute: ${localAttr.idn}`);
|
package/dist/sync/skill-files.js
CHANGED
|
@@ -40,8 +40,8 @@ export async function findSkillScriptFiles(skillFolderPath) {
|
|
|
40
40
|
const stats = await fs.stat(filePath);
|
|
41
41
|
if (stats.isFile()) {
|
|
42
42
|
const ext = path.extname(fileName).toLowerCase();
|
|
43
|
-
// Check for script file extensions
|
|
44
|
-
if (['.jinja', '.guidance', '.nsl'].includes(ext)) {
|
|
43
|
+
// Check for script file extensions (all formats)
|
|
44
|
+
if (['.jinja', '.guidance', '.nsl', '.nslg'].includes(ext)) {
|
|
45
45
|
const content = await fs.readFile(filePath, 'utf8');
|
|
46
46
|
scriptFiles.push({
|
|
47
47
|
filePath,
|
package/dist/types.d.ts
CHANGED
|
@@ -155,10 +155,15 @@ export interface AgentData {
|
|
|
155
155
|
id: string;
|
|
156
156
|
flows: Record<string, FlowData>;
|
|
157
157
|
}
|
|
158
|
+
export interface LibraryData {
|
|
159
|
+
id: string;
|
|
160
|
+
skills: Record<string, SkillMetadata>;
|
|
161
|
+
}
|
|
158
162
|
export interface ProjectData {
|
|
159
163
|
projectId: string;
|
|
160
164
|
projectIdn: string;
|
|
161
165
|
agents: Record<string, AgentData>;
|
|
166
|
+
libraries?: Record<string, LibraryData> | undefined;
|
|
162
167
|
}
|
|
163
168
|
export interface ProjectMap {
|
|
164
169
|
projects: Record<string, ProjectData>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newo",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.1",
|
|
4
4
|
"description": "NEWO CLI: Professional command-line tool with modular architecture for NEWO AI Agent development. Features account migration, integration management, webhook automation, AKB knowledge base, project attributes, sandbox testing, IDN-based file management, real-time progress tracking, intelligent sync operations, and comprehensive multi-customer support.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/api.ts
CHANGED
|
@@ -627,4 +627,68 @@ export async function getLogs(
|
|
|
627
627
|
|
|
628
628
|
const response = await client.get<LogsResponse>(`/api/v1/analytics/logs?${queryParams.toString()}`);
|
|
629
629
|
return response.data;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// ── Libraries ──
|
|
633
|
+
|
|
634
|
+
export interface LibraryResponse {
|
|
635
|
+
id: string;
|
|
636
|
+
idn: string;
|
|
637
|
+
project_id: string;
|
|
638
|
+
skills: Skill[];
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export async function listLibraries(client: AxiosInstance, projectId: string): Promise<LibraryResponse[]> {
|
|
642
|
+
const response = await client.get<LibraryResponse[]>(
|
|
643
|
+
`/api/v1/designer/projects/${projectId}/libraries`
|
|
644
|
+
);
|
|
645
|
+
return response.data;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export async function listLibrarySkills(client: AxiosInstance, libraryId: string): Promise<Skill[]> {
|
|
649
|
+
const response = await client.get<Skill[]>(
|
|
650
|
+
`/api/v1/designer/libraries/${libraryId}/skills`
|
|
651
|
+
);
|
|
652
|
+
return response.data;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
export async function updateLibrarySkill(
|
|
656
|
+
client: AxiosInstance,
|
|
657
|
+
libraryId: string,
|
|
658
|
+
skillId: string,
|
|
659
|
+
data: { prompt_script: string; [key: string]: unknown }
|
|
660
|
+
): Promise<void> {
|
|
661
|
+
await client.patch(
|
|
662
|
+
`/api/v1/designer/libraries/${libraryId}/skills/${skillId}`,
|
|
663
|
+
data
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// ── V2 Bulk Export/Import ──
|
|
668
|
+
|
|
669
|
+
export interface V2ExportOptions {
|
|
670
|
+
export_akb?: boolean;
|
|
671
|
+
export_customer_attributes?: boolean;
|
|
672
|
+
exclude_projects_idn?: string[];
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
export async function exportCustomerV2(
|
|
676
|
+
client: AxiosInstance,
|
|
677
|
+
customerId: string,
|
|
678
|
+
options: V2ExportOptions = {}
|
|
679
|
+
): Promise<Buffer> {
|
|
680
|
+
const params = new URLSearchParams();
|
|
681
|
+
params.set('customer_id', customerId);
|
|
682
|
+
if (options.export_akb !== undefined) params.set('export_akb', String(options.export_akb));
|
|
683
|
+
if (options.export_customer_attributes !== undefined) params.set('export_customer_attributes', String(options.export_customer_attributes));
|
|
684
|
+
if (options.exclude_projects_idn && options.exclude_projects_idn.length > 0) {
|
|
685
|
+
for (const idn of options.exclude_projects_idn) {
|
|
686
|
+
params.append('exclude_projects_idn', idn);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const response = await client.post(`/api/v2/designer/customer/export?${params.toString()}`, null, {
|
|
691
|
+
responseType: 'arraybuffer',
|
|
692
|
+
});
|
|
693
|
+
return Buffer.from(response.data as ArrayBuffer);
|
|
630
694
|
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Export command handler - downloads V2 bulk export ZIP from platform
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* newo export # Export to temp/export-{customerIdn}-{timestamp}.zip
|
|
6
|
+
* newo export --output my-export.zip # Export to specific file
|
|
7
|
+
* newo export --no-akb # Exclude AKB from export
|
|
8
|
+
* newo export --no-attributes # Exclude customer attributes
|
|
9
|
+
*/
|
|
10
|
+
import { makeClient, exportCustomerV2 } from '../../api.js';
|
|
11
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
12
|
+
import { selectSingleCustomer } from '../customer-selection.js';
|
|
13
|
+
import fs from 'fs-extra';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
16
|
+
|
|
17
|
+
export async function handleExportCommand(
|
|
18
|
+
customerConfig: MultiCustomerConfig,
|
|
19
|
+
args: CliArgs,
|
|
20
|
+
verbose: boolean
|
|
21
|
+
): Promise<void> {
|
|
22
|
+
const { selectedCustomer } = selectSingleCustomer(
|
|
23
|
+
customerConfig,
|
|
24
|
+
args.customer as string | undefined
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (!selectedCustomer) {
|
|
28
|
+
console.error('Please specify a customer with --customer');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const accessToken = await getValidAccessToken(selectedCustomer);
|
|
33
|
+
const client = await makeClient(verbose, accessToken);
|
|
34
|
+
|
|
35
|
+
// Get customer ID from token (needed for V2 export API)
|
|
36
|
+
const customerId = await getCustomerIdFromToken(accessToken);
|
|
37
|
+
if (!customerId) {
|
|
38
|
+
console.error('Could not determine customer ID from token');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const exportAkb = !args['no-akb'];
|
|
43
|
+
const exportAttributes = !args['no-attributes'];
|
|
44
|
+
|
|
45
|
+
console.log(`Downloading V2 export for customer ${selectedCustomer.idn}...`);
|
|
46
|
+
|
|
47
|
+
const zipBuffer = await exportCustomerV2(client, customerId, {
|
|
48
|
+
export_akb: exportAkb,
|
|
49
|
+
export_customer_attributes: exportAttributes,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Determine output path
|
|
53
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
54
|
+
const defaultName = `export-${selectedCustomer.idn}-${timestamp}.zip`;
|
|
55
|
+
const outputPath = (args.output as string | undefined) || (args.o as string | undefined)
|
|
56
|
+
|| path.join(process.cwd(), 'temp', defaultName);
|
|
57
|
+
|
|
58
|
+
await fs.ensureDir(path.dirname(outputPath));
|
|
59
|
+
await fs.writeFile(outputPath, zipBuffer);
|
|
60
|
+
|
|
61
|
+
console.log(`Exported ${(zipBuffer.length / 1024 / 1024).toFixed(1)}MB to ${outputPath}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract customer_id from JWT token payload
|
|
66
|
+
*/
|
|
67
|
+
function getCustomerIdFromToken(token: string): string | null {
|
|
68
|
+
try {
|
|
69
|
+
const parts = token.split('.');
|
|
70
|
+
if (parts.length < 2) return null;
|
|
71
|
+
const payload = parts[1]!;
|
|
72
|
+
const padded = payload + '='.repeat(4 - (payload.length % 4));
|
|
73
|
+
const decoded = JSON.parse(Buffer.from(padded, 'base64').toString('utf8')) as Record<string, unknown>;
|
|
74
|
+
return (decoded['customer_id'] as string) || (decoded['sub'] as string) || null;
|
|
75
|
+
} catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|