newo 3.4.1 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.env.example +5 -0
  2. package/CHANGELOG.md +31 -0
  3. package/dist/api.d.ts +18 -0
  4. package/dist/api.js +28 -0
  5. package/dist/cli/commands/create-attribute.js +1 -1
  6. package/dist/cli/commands/export.d.ts +3 -0
  7. package/dist/cli/commands/export.js +62 -0
  8. package/dist/cli/commands/help.js +54 -42
  9. package/dist/cli/commands/pull.js +38 -14
  10. package/dist/cli/commands/push.js +32 -32
  11. package/dist/cli/commands/status.js +46 -7
  12. package/dist/cli/commands/update-attribute.d.ts +3 -0
  13. package/dist/cli/commands/update-attribute.js +78 -0
  14. package/dist/cli-new/bootstrap.d.ts +7 -1
  15. package/dist/cli-new/bootstrap.js +11 -5
  16. package/dist/cli-new/di/tokens.d.ts +1 -0
  17. package/dist/cli-new/di/tokens.js +1 -0
  18. package/dist/cli.js +8 -0
  19. package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +5 -0
  20. package/dist/domain/strategies/sync/ProjectSyncStrategy.js +97 -8
  21. package/dist/domain/strategies/sync/V2ProjectSyncStrategy.d.ts +80 -0
  22. package/dist/domain/strategies/sync/V2ProjectSyncStrategy.js +725 -0
  23. package/dist/env.d.ts +1 -0
  24. package/dist/env.js +1 -0
  25. package/dist/format/detect.d.ts +14 -0
  26. package/dist/format/detect.js +105 -0
  27. package/dist/format/extensions.d.ts +26 -0
  28. package/dist/format/extensions.js +45 -0
  29. package/dist/format/index.d.ts +11 -0
  30. package/dist/format/index.js +11 -0
  31. package/dist/format/paths-v2.d.ts +31 -0
  32. package/dist/format/paths-v2.js +104 -0
  33. package/dist/format/types.d.ts +28 -0
  34. package/dist/format/types.js +21 -0
  35. package/dist/format/v2-yaml.d.ts +143 -0
  36. package/dist/format/v2-yaml.js +222 -0
  37. package/dist/format/yaml-patch.d.ts +14 -0
  38. package/dist/format/yaml-patch.js +184 -0
  39. package/dist/fsutil.d.ts +10 -0
  40. package/dist/fsutil.js +25 -0
  41. package/dist/sync/attributes.js +3 -3
  42. package/dist/sync/skill-files.js +2 -2
  43. package/dist/types.d.ts +5 -0
  44. package/package.json +1 -1
  45. package/src/api.ts +64 -0
  46. package/src/cli/commands/create-attribute.ts +1 -1
  47. package/src/cli/commands/export.ts +78 -0
  48. package/src/cli/commands/help.ts +54 -42
  49. package/src/cli/commands/pull.ts +46 -15
  50. package/src/cli/commands/push.ts +38 -31
  51. package/src/cli/commands/status.ts +59 -9
  52. package/src/cli/commands/update-attribute.ts +82 -0
  53. package/src/cli-new/bootstrap.ts +19 -7
  54. package/src/cli-new/di/tokens.ts +1 -0
  55. package/src/cli.ts +10 -0
  56. package/src/domain/strategies/sync/ProjectSyncStrategy.ts +122 -8
  57. package/src/domain/strategies/sync/V2ProjectSyncStrategy.ts +1007 -0
  58. package/src/env.ts +2 -0
  59. package/src/format/detect.ts +123 -0
  60. package/src/format/extensions.ts +61 -0
  61. package/src/format/index.ts +66 -0
  62. package/src/format/paths-v2.ts +207 -0
  63. package/src/format/types.ts +40 -0
  64. package/src/format/v2-yaml.ts +345 -0
  65. package/src/format/yaml-patch.ts +208 -0
  66. package/src/fsutil.ts +37 -0
  67. package/src/sync/attributes.ts +3 -3
  68. package/src/sync/skill-files.ts +2 -2
  69. package/src/types.ts +6 -0
package/src/env.ts CHANGED
@@ -12,6 +12,7 @@ export interface ValidatedEnv {
12
12
  readonly NEWO_REFRESH_TOKEN: string | undefined;
13
13
  readonly NEWO_REFRESH_URL: string | undefined;
14
14
  readonly NEWO_DEFAULT_CUSTOMER: string | undefined;
15
+ readonly NEWO_FORMAT: string | undefined;
15
16
  // Dynamic customer entries will be detected at runtime
16
17
  readonly [key: string]: string | undefined;
17
18
  }
@@ -81,6 +82,7 @@ export function validateEnvironment(): ValidatedEnv {
81
82
  NEWO_REFRESH_TOKEN: refreshToken,
82
83
  NEWO_REFRESH_URL: refreshUrl,
83
84
  NEWO_DEFAULT_CUSTOMER: env.NEWO_DEFAULT_CUSTOMER?.trim(),
85
+ NEWO_FORMAT: env.NEWO_FORMAT?.trim(),
84
86
  };
85
87
  }
86
88
 
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Format detection and resolution
3
+ *
4
+ * Resolution chain per customer (highest to lowest priority):
5
+ * 1. Explicit --format flag (per-command override)
6
+ * 2. Filesystem auto-detect (for EXISTING customers):
7
+ * - import_version.txt present -> newo_v2
8
+ * - projects/ directory present -> cli_v1
9
+ * 3. NEWO_FORMAT env var (only for NEW customers where nothing exists locally)
10
+ * 4. Default: cli_v1
11
+ */
12
+ import fs from 'fs-extra';
13
+ import path from 'path';
14
+ import { NEWO_CUSTOMERS_DIR } from '../fsutil.js';
15
+ import { type FormatVersion, type FormatConfig, VALID_FORMATS } from './types.js';
16
+
17
+ /**
18
+ * Detect format from existing filesystem structure for a customer
19
+ * Returns null if the customer directory doesn't exist or is empty
20
+ */
21
+ export function detectFormatFromFilesystem(customerIdn: string): FormatVersion | null {
22
+ const customerDir = path.join(NEWO_CUSTOMERS_DIR, customerIdn);
23
+
24
+ if (!fs.existsSync(customerDir)) {
25
+ return null;
26
+ }
27
+
28
+ // Check for V2 marker: import_version.txt
29
+ const importVersionFile = path.join(customerDir, 'import_version.txt');
30
+ if (fs.existsSync(importVersionFile)) {
31
+ return 'newo_v2';
32
+ }
33
+
34
+ // Check for V1 marker: projects/ directory
35
+ const projectsDir = path.join(customerDir, 'projects');
36
+ if (fs.existsSync(projectsDir)) {
37
+ return 'cli_v1';
38
+ }
39
+
40
+ // Check one level down for import_version.txt (V2 exports sometimes have a wrapper dir)
41
+ try {
42
+ const entries = fs.readdirSync(customerDir, { withFileTypes: true });
43
+ for (const entry of entries) {
44
+ if (entry.isDirectory()) {
45
+ const nestedImportVersion = path.join(customerDir, entry.name, 'import_version.txt');
46
+ if (fs.existsSync(nestedImportVersion)) {
47
+ return 'newo_v2';
48
+ }
49
+ }
50
+ }
51
+ } catch {
52
+ // Ignore read errors
53
+ }
54
+
55
+ // Directory exists but no format markers found - could be empty or only has attributes
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * Get format from NEWO_FORMAT environment variable
61
+ */
62
+ function getEnvFormat(): FormatVersion | null {
63
+ const envFormat = process.env['NEWO_FORMAT']?.trim().toLowerCase();
64
+ if (!envFormat) return null;
65
+
66
+ if ((VALID_FORMATS as readonly string[]).includes(envFormat)) {
67
+ return envFormat as FormatVersion;
68
+ }
69
+
70
+ console.warn(
71
+ `Warning: Invalid NEWO_FORMAT="${envFormat}". Valid values: ${VALID_FORMATS.join(', ')}. Using default.`
72
+ );
73
+ return null;
74
+ }
75
+
76
+ /**
77
+ * Validate an explicit format string from --format flag
78
+ */
79
+ function validateExplicitFormat(format: string): FormatVersion | null {
80
+ const normalized = format.trim().toLowerCase();
81
+ if ((VALID_FORMATS as readonly string[]).includes(normalized)) {
82
+ return normalized as FormatVersion;
83
+ }
84
+ return null;
85
+ }
86
+
87
+ /**
88
+ * Resolve the format version for a customer using the full resolution chain
89
+ *
90
+ * @param customerIdn - Customer identifier
91
+ * @param explicitFormat - Optional --format flag value
92
+ */
93
+ export function resolveFormat(
94
+ customerIdn: string,
95
+ explicitFormat?: string
96
+ ): FormatConfig {
97
+ // 1. Explicit --format flag takes highest priority
98
+ if (explicitFormat) {
99
+ const validated = validateExplicitFormat(explicitFormat);
100
+ if (validated) {
101
+ return { version: validated, source: 'explicit-flag' };
102
+ }
103
+ console.error(
104
+ `Invalid format "${explicitFormat}". Valid values: ${VALID_FORMATS.join(', ')}`
105
+ );
106
+ process.exit(1);
107
+ }
108
+
109
+ // 2. Filesystem auto-detect for existing customers
110
+ const detected = detectFormatFromFilesystem(customerIdn);
111
+ if (detected) {
112
+ return { version: detected, source: 'auto-detected' };
113
+ }
114
+
115
+ // 3. NEWO_FORMAT env var (for new customers only)
116
+ const envFormat = getEnvFormat();
117
+ if (envFormat) {
118
+ return { version: envFormat, source: 'env-var' };
119
+ }
120
+
121
+ // 4. Default: cli_v1
122
+ return { version: 'cli_v1', source: 'default' };
123
+ }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Format-aware file extension mapping
3
+ *
4
+ * Handles the extension differences between cli_v1 and newo_v2:
5
+ * cli_v1: guidance -> .guidance, nsl -> .jinja
6
+ * newo_v2: guidance -> .nslg, nsl -> .nsl
7
+ */
8
+ import type { RunnerType } from '../types.js';
9
+ import {
10
+ type FormatVersion,
11
+ CLI_V1_EXTENSIONS,
12
+ NEWO_V2_EXTENSIONS,
13
+ ALL_SCRIPT_EXTENSIONS,
14
+ } from './types.js';
15
+
16
+ /**
17
+ * Get file extension for a runner type in a specific format
18
+ */
19
+ export function getExtensionForFormat(runnerType: RunnerType, format: FormatVersion): string {
20
+ const map = format === 'newo_v2' ? NEWO_V2_EXTENSIONS : CLI_V1_EXTENSIONS;
21
+ const ext = map[runnerType];
22
+ return ext ?? CLI_V1_EXTENSIONS.guidance;
23
+ }
24
+
25
+ /**
26
+ * Get runner type from file extension (works for all 4 extensions)
27
+ */
28
+ export function getRunnerTypeFromExtension(ext: string): RunnerType {
29
+ const normalized = ext.startsWith('.') ? ext : `.${ext}`;
30
+ switch (normalized) {
31
+ case '.guidance':
32
+ case '.nslg':
33
+ return 'guidance';
34
+ case '.jinja':
35
+ case '.nsl':
36
+ return 'nsl';
37
+ default:
38
+ return 'guidance';
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Check if a filename is a recognized script file (any format)
44
+ */
45
+ export function isScriptFile(filename: string): boolean {
46
+ const ext = filename.lastIndexOf('.') >= 0
47
+ ? filename.slice(filename.lastIndexOf('.'))
48
+ : '';
49
+ return (ALL_SCRIPT_EXTENSIONS as readonly string[]).includes(ext.toLowerCase());
50
+ }
51
+
52
+ /**
53
+ * Infer format version from a file extension
54
+ */
55
+ export function getFormatFromExtension(ext: string): FormatVersion {
56
+ const normalized = ext.startsWith('.') ? ext : `.${ext}`;
57
+ if (normalized === '.nsl' || normalized === '.nslg') {
58
+ return 'newo_v2';
59
+ }
60
+ return 'cli_v1';
61
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Format module - handles cli_v1 and newo_v2 format differences
3
+ */
4
+ export {
5
+ type FormatVersion,
6
+ type FormatConfig,
7
+ CLI_V1_EXTENSIONS,
8
+ NEWO_V2_EXTENSIONS,
9
+ ALL_SCRIPT_EXTENSIONS,
10
+ V2_IMPORT_VERSION,
11
+ VALID_FORMATS,
12
+ } from './types.js';
13
+
14
+ export {
15
+ getExtensionForFormat,
16
+ getRunnerTypeFromExtension,
17
+ isScriptFile,
18
+ getFormatFromExtension,
19
+ } from './extensions.js';
20
+
21
+ export {
22
+ detectFormatFromFilesystem,
23
+ resolveFormat,
24
+ } from './detect.js';
25
+
26
+ export {
27
+ v2CustomerDir,
28
+ v2ProjectDir,
29
+ v2AgentDir,
30
+ v2FlowDir,
31
+ v2SkillsDir,
32
+ v2SkillScriptPath,
33
+ v2FlowYamlPath,
34
+ v2ProjectYamlPath,
35
+ v2AgentYamlPath,
36
+ v2ImportVersionPath,
37
+ v2AkbDir,
38
+ v2AkbPath,
39
+ v2CustomerAttributesPath,
40
+ v2ProjectAttributesPath,
41
+ v2LibraryDir,
42
+ v2LibraryYamlPath,
43
+ v2LibrarySkillsDir,
44
+ v2LibrarySkillScriptPath,
45
+ } from './paths-v2.js';
46
+
47
+ export { patchYamlToPyyaml } from './yaml-patch.js';
48
+ export { v2LibrarySkillRelativePath } from './paths-v2.js';
49
+
50
+ export {
51
+ type V2FlowDefinition,
52
+ type V2InlineSkill,
53
+ type V2FlowEvent,
54
+ type V2StateField,
55
+ type V2ProjectMeta,
56
+ type V2AgentMeta,
57
+ type V2LibraryDefinition,
58
+ parseV2FlowYaml,
59
+ generateV2FlowYaml,
60
+ parseV2ProjectYaml,
61
+ generateV2ProjectYaml,
62
+ parseV2AgentYaml,
63
+ generateV2AgentYaml,
64
+ parseV2LibraryYaml,
65
+ generateV2LibraryYaml,
66
+ } from './v2-yaml.js';
@@ -0,0 +1,207 @@
1
+ /**
2
+ * V2 (newo_v2) path utilities
3
+ *
4
+ * Generates paths for the NEWO platform export format:
5
+ * newo_customers/{cust}/
6
+ * import_version.txt
7
+ * attributes.yaml
8
+ * akb/{AgentIdn}.yaml
9
+ * {ProjectIdn}/
10
+ * {project_idn}.yaml
11
+ * attributes.yaml
12
+ * agents/{AgentIdn}/
13
+ * agent.yaml
14
+ * flows/{FlowIdn}/
15
+ * {FlowIdn}.yaml
16
+ * skills/{SkillIdn}.nsl|.nslg
17
+ * libraries/{LibIdn}/
18
+ * {lib_idn}.yaml
19
+ * skills/{SkillIdn}.nsl|.nslg
20
+ */
21
+ import path from 'path';
22
+ import { NEWO_CUSTOMERS_DIR } from '../fsutil.js';
23
+ import type { RunnerType } from '../types.js';
24
+ import { getExtensionForFormat } from './extensions.js';
25
+
26
+ // ── Customer level ──
27
+
28
+ export function v2CustomerDir(customerIdn: string): string {
29
+ return path.posix.join(NEWO_CUSTOMERS_DIR, customerIdn);
30
+ }
31
+
32
+ export function v2ImportVersionPath(customerIdn: string): string {
33
+ return path.posix.join(v2CustomerDir(customerIdn), 'import_version.txt');
34
+ }
35
+
36
+ export function v2CustomerAttributesPath(customerIdn: string): string {
37
+ return path.posix.join(v2CustomerDir(customerIdn), 'attributes.yaml');
38
+ }
39
+
40
+ // ── AKB level ──
41
+
42
+ export function v2AkbDir(customerIdn: string): string {
43
+ return path.posix.join(v2CustomerDir(customerIdn), 'akb');
44
+ }
45
+
46
+ export function v2AkbPath(customerIdn: string, agentIdn: string): string {
47
+ return path.posix.join(v2AkbDir(customerIdn), `${agentIdn}.yaml`);
48
+ }
49
+
50
+ // ── Project level ──
51
+
52
+ export function v2ProjectDir(customerIdn: string, projectIdn: string): string {
53
+ return path.posix.join(v2CustomerDir(customerIdn), projectIdn);
54
+ }
55
+
56
+ export function v2ProjectYamlPath(customerIdn: string, projectIdn: string): string {
57
+ return path.posix.join(v2ProjectDir(customerIdn, projectIdn), `${projectIdn}.yaml`);
58
+ }
59
+
60
+ export function v2ProjectAttributesPath(customerIdn: string, projectIdn: string): string {
61
+ return path.posix.join(v2ProjectDir(customerIdn, projectIdn), 'attributes.yaml');
62
+ }
63
+
64
+ // ── Agent level ──
65
+
66
+ export function v2AgentDir(
67
+ customerIdn: string,
68
+ projectIdn: string,
69
+ agentIdn: string
70
+ ): string {
71
+ return path.posix.join(v2ProjectDir(customerIdn, projectIdn), 'agents', agentIdn);
72
+ }
73
+
74
+ export function v2AgentYamlPath(
75
+ customerIdn: string,
76
+ projectIdn: string,
77
+ agentIdn: string
78
+ ): string {
79
+ return path.posix.join(v2AgentDir(customerIdn, projectIdn, agentIdn), 'agent.yaml');
80
+ }
81
+
82
+ // ── Flow level ──
83
+
84
+ export function v2FlowDir(
85
+ customerIdn: string,
86
+ projectIdn: string,
87
+ agentIdn: string,
88
+ flowIdn: string
89
+ ): string {
90
+ return path.posix.join(
91
+ v2AgentDir(customerIdn, projectIdn, agentIdn),
92
+ 'flows',
93
+ flowIdn
94
+ );
95
+ }
96
+
97
+ export function v2FlowYamlPath(
98
+ customerIdn: string,
99
+ projectIdn: string,
100
+ agentIdn: string,
101
+ flowIdn: string
102
+ ): string {
103
+ return path.posix.join(
104
+ v2FlowDir(customerIdn, projectIdn, agentIdn, flowIdn),
105
+ `${flowIdn}.yaml`
106
+ );
107
+ }
108
+
109
+ // ── Skill level ──
110
+
111
+ export function v2SkillsDir(
112
+ customerIdn: string,
113
+ projectIdn: string,
114
+ agentIdn: string,
115
+ flowIdn: string
116
+ ): string {
117
+ return path.posix.join(
118
+ v2FlowDir(customerIdn, projectIdn, agentIdn, flowIdn),
119
+ 'skills'
120
+ );
121
+ }
122
+
123
+ export function v2SkillScriptPath(
124
+ customerIdn: string,
125
+ projectIdn: string,
126
+ agentIdn: string,
127
+ flowIdn: string,
128
+ skillIdn: string,
129
+ runnerType: RunnerType
130
+ ): string {
131
+ const ext = getExtensionForFormat(runnerType, 'newo_v2');
132
+ return path.posix.join(
133
+ v2SkillsDir(customerIdn, projectIdn, agentIdn, flowIdn),
134
+ `${skillIdn}${ext}`
135
+ );
136
+ }
137
+
138
+ /**
139
+ * Build the relative prompt_script path as it appears in V2 flow YAML
140
+ * e.g., "flows/MainFlow/skills/GreetingSkill.nsl"
141
+ */
142
+ export function v2SkillRelativePath(
143
+ flowIdn: string,
144
+ skillIdn: string,
145
+ runnerType: RunnerType
146
+ ): string {
147
+ const ext = getExtensionForFormat(runnerType, 'newo_v2');
148
+ return `flows/${flowIdn}/skills/${skillIdn}${ext}`;
149
+ }
150
+
151
+ // ── Library level ──
152
+
153
+ export function v2LibraryDir(
154
+ customerIdn: string,
155
+ projectIdn: string,
156
+ libraryIdn: string
157
+ ): string {
158
+ return path.posix.join(v2ProjectDir(customerIdn, projectIdn), 'libraries', libraryIdn);
159
+ }
160
+
161
+ export function v2LibraryYamlPath(
162
+ customerIdn: string,
163
+ projectIdn: string,
164
+ libraryIdn: string
165
+ ): string {
166
+ return path.posix.join(
167
+ v2LibraryDir(customerIdn, projectIdn, libraryIdn),
168
+ `${libraryIdn}.yaml`
169
+ );
170
+ }
171
+
172
+ export function v2LibrarySkillsDir(
173
+ customerIdn: string,
174
+ projectIdn: string,
175
+ libraryIdn: string
176
+ ): string {
177
+ return path.posix.join(v2LibraryDir(customerIdn, projectIdn, libraryIdn), 'skills');
178
+ }
179
+
180
+ export function v2LibrarySkillScriptPath(
181
+ customerIdn: string,
182
+ projectIdn: string,
183
+ libraryIdn: string,
184
+ skillIdn: string,
185
+ runnerType: RunnerType
186
+ ): string {
187
+ const ext = getExtensionForFormat(runnerType, 'newo_v2');
188
+ return path.posix.join(
189
+ v2LibrarySkillsDir(customerIdn, projectIdn, libraryIdn),
190
+ `${skillIdn}${ext}`
191
+ );
192
+ }
193
+
194
+ /**
195
+ * Build relative prompt_script path for library skill in V2 YAML
196
+ * The V2 export includes the project prefix:
197
+ * e.g., "naf/libraries/testLib/skills/utilSkill.nsl"
198
+ */
199
+ export function v2LibrarySkillRelativePath(
200
+ projectIdn: string,
201
+ libraryIdn: string,
202
+ skillIdn: string,
203
+ runnerType: RunnerType
204
+ ): string {
205
+ const ext = getExtensionForFormat(runnerType, 'newo_v2');
206
+ return `${projectIdn}/libraries/${libraryIdn}/skills/${skillIdn}${ext}`;
207
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Format version types and constants
3
+ *
4
+ * Defines the two supported project formats:
5
+ * - cli_v1: Native CLI format (full feature support, per-entity metadata)
6
+ * - newo_v2: NEWO platform export format (compatible with platform UI exports)
7
+ */
8
+ import type { RunnerType } from '../types.js';
9
+
10
+ export type FormatVersion = 'cli_v1' | 'newo_v2';
11
+
12
+ export interface FormatConfig {
13
+ version: FormatVersion;
14
+ source: 'explicit-flag' | 'env-var' | 'auto-detected' | 'default';
15
+ }
16
+
17
+ /**
18
+ * Extension mapping per format
19
+ *
20
+ * cli_v1: guidance -> .guidance, nsl -> .jinja
21
+ * newo_v2: guidance -> .nslg, nsl -> .nsl
22
+ */
23
+ export const CLI_V1_EXTENSIONS: Record<RunnerType, string> = {
24
+ guidance: '.guidance',
25
+ nsl: '.jinja',
26
+ } as const;
27
+
28
+ export const NEWO_V2_EXTENSIONS: Record<RunnerType, string> = {
29
+ guidance: '.nslg',
30
+ nsl: '.nsl',
31
+ } as const;
32
+
33
+ /** All recognized script file extensions across both formats */
34
+ export const ALL_SCRIPT_EXTENSIONS = ['.guidance', '.jinja', '.nsl', '.nslg'] as const;
35
+
36
+ /** V2 import version marker content */
37
+ export const V2_IMPORT_VERSION = 'v2.0.0';
38
+
39
+ /** Valid format values for CLI flag and env var validation */
40
+ export const VALID_FORMATS: readonly FormatVersion[] = ['cli_v1', 'newo_v2'] as const;