newo 3.4.0 → 3.4.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/api.d.ts +3 -1
  3. package/dist/api.js +49 -1
  4. package/dist/application/migration/MigrationEngine.d.ts +141 -0
  5. package/dist/application/migration/MigrationEngine.js +322 -0
  6. package/dist/application/migration/index.d.ts +5 -0
  7. package/dist/application/migration/index.js +5 -0
  8. package/dist/application/sync/SyncEngine.d.ts +134 -0
  9. package/dist/application/sync/SyncEngine.js +335 -0
  10. package/dist/application/sync/index.d.ts +5 -0
  11. package/dist/application/sync/index.js +5 -0
  12. package/dist/cli/commands/create-attribute.js +1 -1
  13. package/dist/cli/commands/create-customer.d.ts +3 -0
  14. package/dist/cli/commands/create-customer.js +159 -0
  15. package/dist/cli/commands/diff.d.ts +6 -0
  16. package/dist/cli/commands/diff.js +288 -0
  17. package/dist/cli/commands/help.js +63 -3
  18. package/dist/cli/commands/logs.d.ts +18 -0
  19. package/dist/cli/commands/logs.js +283 -0
  20. package/dist/cli/commands/pull.js +114 -10
  21. package/dist/cli/commands/push.js +122 -12
  22. package/dist/cli/commands/update-attribute.d.ts +3 -0
  23. package/dist/cli/commands/update-attribute.js +78 -0
  24. package/dist/cli/commands/watch.d.ts +6 -0
  25. package/dist/cli/commands/watch.js +195 -0
  26. package/dist/cli-new/bootstrap.d.ts +74 -0
  27. package/dist/cli-new/bootstrap.js +154 -0
  28. package/dist/cli-new/di/Container.d.ts +64 -0
  29. package/dist/cli-new/di/Container.js +122 -0
  30. package/dist/cli-new/di/tokens.d.ts +77 -0
  31. package/dist/cli-new/di/tokens.js +76 -0
  32. package/dist/cli.js +20 -0
  33. package/dist/domain/resources/common/types.d.ts +71 -0
  34. package/dist/domain/resources/common/types.js +42 -0
  35. package/dist/domain/strategies/sync/AkbSyncStrategy.d.ts +63 -0
  36. package/dist/domain/strategies/sync/AkbSyncStrategy.js +274 -0
  37. package/dist/domain/strategies/sync/AttributeSyncStrategy.d.ts +87 -0
  38. package/dist/domain/strategies/sync/AttributeSyncStrategy.js +378 -0
  39. package/dist/domain/strategies/sync/ConversationSyncStrategy.d.ts +61 -0
  40. package/dist/domain/strategies/sync/ConversationSyncStrategy.js +232 -0
  41. package/dist/domain/strategies/sync/ISyncStrategy.d.ts +149 -0
  42. package/dist/domain/strategies/sync/ISyncStrategy.js +24 -0
  43. package/dist/domain/strategies/sync/IntegrationSyncStrategy.d.ts +68 -0
  44. package/dist/domain/strategies/sync/IntegrationSyncStrategy.js +413 -0
  45. package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +111 -0
  46. package/dist/domain/strategies/sync/ProjectSyncStrategy.js +523 -0
  47. package/dist/domain/strategies/sync/index.d.ts +13 -0
  48. package/dist/domain/strategies/sync/index.js +19 -0
  49. package/dist/sync/migrate.js +99 -23
  50. package/dist/types.d.ts +124 -0
  51. package/package.json +3 -1
  52. package/src/api.ts +53 -2
  53. package/src/application/migration/MigrationEngine.ts +492 -0
  54. package/src/application/migration/index.ts +5 -0
  55. package/src/application/sync/SyncEngine.ts +467 -0
  56. package/src/application/sync/index.ts +5 -0
  57. package/src/cli/commands/create-attribute.ts +1 -1
  58. package/src/cli/commands/create-customer.ts +185 -0
  59. package/src/cli/commands/diff.ts +360 -0
  60. package/src/cli/commands/help.ts +63 -3
  61. package/src/cli/commands/logs.ts +329 -0
  62. package/src/cli/commands/pull.ts +128 -11
  63. package/src/cli/commands/push.ts +131 -13
  64. package/src/cli/commands/update-attribute.ts +82 -0
  65. package/src/cli/commands/watch.ts +227 -0
  66. package/src/cli-new/bootstrap.ts +252 -0
  67. package/src/cli-new/di/Container.ts +152 -0
  68. package/src/cli-new/di/tokens.ts +105 -0
  69. package/src/cli.ts +25 -0
  70. package/src/domain/resources/common/types.ts +106 -0
  71. package/src/domain/strategies/sync/AkbSyncStrategy.ts +358 -0
  72. package/src/domain/strategies/sync/AttributeSyncStrategy.ts +508 -0
  73. package/src/domain/strategies/sync/ConversationSyncStrategy.ts +299 -0
  74. package/src/domain/strategies/sync/ISyncStrategy.ts +182 -0
  75. package/src/domain/strategies/sync/IntegrationSyncStrategy.ts +522 -0
  76. package/src/domain/strategies/sync/ProjectSyncStrategy.ts +747 -0
  77. package/src/domain/strategies/sync/index.ts +46 -0
  78. package/src/sync/migrate.ts +103 -24
  79. package/src/types.ts +135 -0
@@ -0,0 +1,360 @@
1
+ /**
2
+ * Diff command handler
3
+ *
4
+ * Shows differences between local files and remote NEWO platform.
5
+ * Supports selective resource diffing with --only flag.
6
+ *
7
+ * Usage:
8
+ * newo diff # Show all differences
9
+ * newo diff --only projects # Show only project differences
10
+ * newo diff --detailed # Show detailed content diff
11
+ */
12
+ import { makeClient } from '../../api.js';
13
+ import { getValidAccessToken } from '../../auth.js';
14
+ import { selectSingleCustomer } from '../customer-selection.js';
15
+ import { ALL_RESOURCE_TYPES } from '../../cli-new/di/tokens.js';
16
+ import type { MultiCustomerConfig, CliArgs, CustomerConfig } from '../../types.js';
17
+ import type { AxiosInstance } from 'axios';
18
+ import { getSkill, listAgents, listFlowSkills, getCustomerAttributes, listProjects } from '../../api.js';
19
+ import fs from 'fs-extra';
20
+ import path from 'path';
21
+ import yaml from 'js-yaml';
22
+
23
+ /**
24
+ * Parse resource list from comma-separated string
25
+ */
26
+ function parseResourceList(input: string | undefined): string[] {
27
+ if (!input) return [];
28
+ return input.split(',').map(r => r.trim().toLowerCase()).filter(Boolean);
29
+ }
30
+
31
+ /**
32
+ * Color codes for terminal output
33
+ */
34
+ const colors = {
35
+ reset: '\x1b[0m',
36
+ red: '\x1b[31m',
37
+ green: '\x1b[32m',
38
+ yellow: '\x1b[33m',
39
+ blue: '\x1b[34m',
40
+ cyan: '\x1b[36m',
41
+ gray: '\x1b[90m',
42
+ };
43
+
44
+ /**
45
+ * Diff entry for display
46
+ */
47
+ interface DiffEntry {
48
+ path: string;
49
+ type: 'added' | 'modified' | 'deleted' | 'unchanged';
50
+ localContent?: string;
51
+ remoteContent?: string;
52
+ }
53
+
54
+ /**
55
+ * Generate unified diff output
56
+ */
57
+ function generateUnifiedDiff(local: string, remote: string, filePath: string): string[] {
58
+ const localLines = local.split('\n');
59
+ const remoteLines = remote.split('\n');
60
+ const output: string[] = [];
61
+
62
+ output.push(`${colors.cyan}--- local: ${filePath}${colors.reset}`);
63
+ output.push(`${colors.cyan}+++ remote: ${filePath}${colors.reset}`);
64
+
65
+ // Simple line-by-line comparison
66
+ const maxLines = Math.max(localLines.length, remoteLines.length);
67
+ let diffStart = -1;
68
+ let diffLines: string[] = [];
69
+
70
+ for (let i = 0; i < maxLines; i++) {
71
+ const localLine = localLines[i] ?? '';
72
+ const remoteLine = remoteLines[i] ?? '';
73
+
74
+ if (localLine !== remoteLine) {
75
+ if (diffStart === -1) {
76
+ diffStart = i + 1;
77
+ }
78
+ if (i < localLines.length) {
79
+ diffLines.push(`${colors.red}- ${localLine}${colors.reset}`);
80
+ }
81
+ if (i < remoteLines.length) {
82
+ diffLines.push(`${colors.green}+ ${remoteLine}${colors.reset}`);
83
+ }
84
+ } else {
85
+ if (diffStart !== -1) {
86
+ output.push(`${colors.gray}@@ -${diffStart},${diffLines.length} @@${colors.reset}`);
87
+ output.push(...diffLines);
88
+ diffStart = -1;
89
+ diffLines = [];
90
+ }
91
+ }
92
+ }
93
+
94
+ if (diffLines.length > 0) {
95
+ output.push(`${colors.gray}@@ -${diffStart},${diffLines.length} @@${colors.reset}`);
96
+ output.push(...diffLines);
97
+ }
98
+
99
+ return output;
100
+ }
101
+
102
+ /**
103
+ * Get project differences
104
+ */
105
+ async function getProjectDiffs(
106
+ client: AxiosInstance,
107
+ customer: CustomerConfig,
108
+ _verbose: boolean
109
+ ): Promise<DiffEntry[]> {
110
+ const diffs: DiffEntry[] = [];
111
+ const customerDir = path.join(process.cwd(), 'newo_customers', customer.idn);
112
+
113
+ // Get all projects first
114
+ const projects = await listProjects(client);
115
+
116
+ for (const project of projects) {
117
+ const projectIdn = project.idn;
118
+
119
+ // Get all agents for this project
120
+ const agents = await listAgents(client, project.id);
121
+
122
+ for (const agent of agents) {
123
+ for (const flow of agent.flows || []) {
124
+ const skills = await listFlowSkills(client, flow.id);
125
+
126
+ for (const skill of skills) {
127
+ // Get full skill content from API
128
+ const remoteSkill = await getSkill(client, skill.id);
129
+ const remoteContent = remoteSkill.prompt_script || '';
130
+
131
+ // Determine local file path
132
+ const extension = remoteSkill.runner_type === 'nsl' ? 'jinja' : 'guidance';
133
+ const localPath = path.join(
134
+ customerDir,
135
+ 'projects',
136
+ projectIdn,
137
+ agent.idn,
138
+ flow.idn,
139
+ skill.idn,
140
+ `${skill.idn}.${extension}`
141
+ );
142
+
143
+ // Compare with local
144
+ if (await fs.pathExists(localPath)) {
145
+ const localContent = await fs.readFile(localPath, 'utf-8');
146
+
147
+ if (localContent !== remoteContent) {
148
+ diffs.push({
149
+ path: path.relative(process.cwd(), localPath),
150
+ type: 'modified',
151
+ localContent,
152
+ remoteContent,
153
+ });
154
+ }
155
+ } else {
156
+ // File exists remotely but not locally
157
+ diffs.push({
158
+ path: path.relative(process.cwd(), localPath),
159
+ type: 'deleted',
160
+ remoteContent,
161
+ });
162
+ }
163
+ }
164
+ }
165
+ }
166
+ }
167
+
168
+ // Check for local files that don't exist remotely
169
+ const projectsDir = path.join(customerDir, 'projects');
170
+ if (await fs.pathExists(projectsDir)) {
171
+ const walkDir = async (dir: string): Promise<string[]> => {
172
+ const files: string[] = [];
173
+ const entries = await fs.readdir(dir, { withFileTypes: true });
174
+ for (const entry of entries) {
175
+ const fullPath = path.join(dir, entry.name);
176
+ if (entry.isDirectory()) {
177
+ files.push(...await walkDir(fullPath));
178
+ } else if (entry.name.endsWith('.guidance') || entry.name.endsWith('.jinja')) {
179
+ files.push(fullPath);
180
+ }
181
+ }
182
+ return files;
183
+ };
184
+
185
+ const localFiles = await walkDir(projectsDir);
186
+ for (const localFile of localFiles) {
187
+ const relativePath = path.relative(process.cwd(), localFile);
188
+ const alreadyInDiffs = diffs.some(d => d.path === relativePath);
189
+ if (!alreadyInDiffs) {
190
+ const localContent = await fs.readFile(localFile, 'utf-8');
191
+ diffs.push({
192
+ path: relativePath,
193
+ type: 'added',
194
+ localContent,
195
+ });
196
+ }
197
+ }
198
+ }
199
+
200
+ return diffs;
201
+ }
202
+
203
+ /**
204
+ * Get attribute differences
205
+ */
206
+ async function getAttributeDiffs(
207
+ client: AxiosInstance,
208
+ customer: CustomerConfig,
209
+ _verbose: boolean
210
+ ): Promise<DiffEntry[]> {
211
+ const diffs: DiffEntry[] = [];
212
+ const customerDir = path.join(process.cwd(), 'newo_customers', customer.idn);
213
+
214
+ // Customer attributes
215
+ const localAttrPath = path.join(customerDir, 'attributes.yaml');
216
+ if (await fs.pathExists(localAttrPath)) {
217
+ try {
218
+ const remoteAttrsResponse = await getCustomerAttributes(client, true);
219
+ const localContent = await fs.readFile(localAttrPath, 'utf-8');
220
+
221
+ // Transform remote to YAML format for comparison
222
+ const remoteYaml = yaml.dump(remoteAttrsResponse.attributes.map(attr => ({
223
+ idn: attr.idn,
224
+ title: attr.title,
225
+ value: attr.value,
226
+ value_type: attr.value_type,
227
+ is_hidden: attr.is_hidden,
228
+ })), { lineWidth: -1 });
229
+
230
+ const localParsed = yaml.load(localContent);
231
+ const localYaml = yaml.dump(localParsed, { lineWidth: -1 });
232
+
233
+ if (localYaml !== remoteYaml) {
234
+ diffs.push({
235
+ path: path.relative(process.cwd(), localAttrPath),
236
+ type: 'modified',
237
+ localContent: localYaml,
238
+ remoteContent: remoteYaml,
239
+ });
240
+ }
241
+ } catch {
242
+ // Error fetching remote - skip
243
+ }
244
+ }
245
+
246
+ return diffs;
247
+ }
248
+
249
+ /**
250
+ * Main diff command handler
251
+ */
252
+ export async function handleDiffCommand(
253
+ customerConfig: MultiCustomerConfig,
254
+ args: CliArgs,
255
+ verbose: boolean
256
+ ): Promise<void> {
257
+ const { selectedCustomer } = selectSingleCustomer(
258
+ customerConfig,
259
+ args.customer as string | undefined
260
+ );
261
+
262
+ if (!selectedCustomer) {
263
+ console.error('❌ Please specify a customer with --customer <idn> or set a default');
264
+ process.exit(1);
265
+ }
266
+
267
+ // Parse options
268
+ const onlyResources = parseResourceList(args.only as string | undefined);
269
+ const detailed = Boolean(args.detailed || args.d);
270
+
271
+ // Determine resources to diff
272
+ let resourcesToDiff: string[];
273
+ if (onlyResources.length > 0) {
274
+ resourcesToDiff = onlyResources.filter(r => ALL_RESOURCE_TYPES.includes(r as typeof ALL_RESOURCE_TYPES[number]));
275
+ } else {
276
+ resourcesToDiff = ['projects', 'attributes']; // Default to main resources
277
+ }
278
+
279
+ console.log(`🔍 Comparing local vs remote for: ${resourcesToDiff.join(', ')}`);
280
+ console.log(`📁 Customer: ${selectedCustomer.idn}`);
281
+ console.log('');
282
+
283
+ const accessToken = await getValidAccessToken(selectedCustomer);
284
+ const client = await makeClient(verbose, accessToken);
285
+
286
+ const allDiffs: DiffEntry[] = [];
287
+
288
+ // Get diffs for each resource type
289
+ for (const resource of resourcesToDiff) {
290
+ switch (resource) {
291
+ case 'projects':
292
+ console.log(`📦 Checking projects...`);
293
+ const projectDiffs = await getProjectDiffs(client, selectedCustomer, verbose);
294
+ allDiffs.push(...projectDiffs);
295
+ break;
296
+ case 'attributes':
297
+ console.log(`📋 Checking attributes...`);
298
+ const attrDiffs = await getAttributeDiffs(client, selectedCustomer, verbose);
299
+ allDiffs.push(...attrDiffs);
300
+ break;
301
+ default:
302
+ console.log(`⏭️ Skipping ${resource} (diff not implemented yet)`);
303
+ }
304
+ }
305
+
306
+ // Display results
307
+ console.log('');
308
+
309
+ if (allDiffs.length === 0) {
310
+ console.log('✅ No differences found. Local and remote are in sync.');
311
+ return;
312
+ }
313
+
314
+ console.log(`📊 Found ${allDiffs.length} difference(s):`);
315
+ console.log('');
316
+
317
+ // Group by type
318
+ const added = allDiffs.filter(d => d.type === 'added');
319
+ const modified = allDiffs.filter(d => d.type === 'modified');
320
+ const deleted = allDiffs.filter(d => d.type === 'deleted');
321
+
322
+ if (added.length > 0) {
323
+ console.log(`${colors.green}➕ Added locally (${added.length}):${colors.reset}`);
324
+ for (const diff of added) {
325
+ console.log(` ${diff.path}`);
326
+ }
327
+ console.log('');
328
+ }
329
+
330
+ if (modified.length > 0) {
331
+ console.log(`${colors.yellow}📝 Modified (${modified.length}):${colors.reset}`);
332
+ for (const diff of modified) {
333
+ console.log(` ${diff.path}`);
334
+
335
+ if (detailed && diff.localContent && diff.remoteContent) {
336
+ const diffOutput = generateUnifiedDiff(diff.localContent, diff.remoteContent, diff.path);
337
+ diffOutput.forEach(line => console.log(` ${line}`));
338
+ console.log('');
339
+ }
340
+ }
341
+ console.log('');
342
+ }
343
+
344
+ if (deleted.length > 0) {
345
+ console.log(`${colors.red}➖ Deleted locally (${deleted.length}):${colors.reset}`);
346
+ for (const diff of deleted) {
347
+ console.log(` ${diff.path}`);
348
+ }
349
+ console.log('');
350
+ }
351
+
352
+ // Summary
353
+ console.log(`${colors.cyan}Summary:${colors.reset}`);
354
+ console.log(` Added: ${added.length}`);
355
+ console.log(` Modified: ${modified.length}`);
356
+ console.log(` Deleted: ${deleted.length}`);
357
+ console.log('');
358
+ console.log(`💡 Run ${colors.cyan}newo push${colors.reset} to upload local changes to remote.`);
359
+ console.log(`💡 Run ${colors.cyan}newo pull${colors.reset} to download remote changes to local.`);
360
+ }
@@ -10,10 +10,13 @@ Core Commands:
10
10
  newo pull [--customer <idn>] # download projects + attributes -> ./newo_customers/<idn>/
11
11
  newo push [--customer <idn>] [--no-publish] # upload modified *.guidance/*.jinja + attributes back to NEWO, publish flows by default
12
12
  newo status [--customer <idn>] # show modified files that would be pushed
13
+ newo watch [--customer <idn>] # watch for file changes and auto-push (NEW)
14
+ newo diff [--customer <idn>] # show differences between local and remote (NEW)
15
+ newo logs [--customer <idn>] # fetch and display analytics logs from platform (NEW)
13
16
  newo conversations [--customer <idn>] [--all] # download user conversations -> ./newo_customers/<idn>/conversations.yaml
14
- newo sandbox "<message>" [--customer <idn>] # test agent in sandbox - single message mode (NEW v3.1.0)
17
+ newo sandbox "<message>" [--customer <idn>] # test agent in sandbox - single message mode
15
18
  newo sandbox --actor <id> "message" # continue existing sandbox conversation with chat ID
16
- newo pull-attributes [--customer <idn>] # download customer + project attributes -> ./newo_customers/<idn>/attributes.yaml + projects/{project}/attributes.yaml
19
+ newo pull-attributes [--customer <idn>] # download customer + project attributes
17
20
  newo list-customers # list available customers and their configuration
18
21
  newo meta [--customer <idn>] # get project metadata (debug command)
19
22
  newo import-akb <file> <persona_id> [--customer <idn>] # import AKB articles from structured text file
@@ -52,7 +55,20 @@ Enterprise Features:
52
55
  newo pull-akb [--customer <idn>] # download AKB articles for all personas with agents → ./newo_customers/<idn>/akb/
53
56
  newo push-akb [--customer <idn>] # upload AKB articles from local YAML files to platform
54
57
 
55
- Account Migration (NEW):
58
+ Analytics & Monitoring (NEW):
59
+ newo logs [--customer <idn>] # fetch last 1 hour of analytics logs
60
+ newo logs --hours <n> # fetch logs from last N hours
61
+ newo logs --from <datetime> --to <datetime> # fetch logs in datetime range (ISO format)
62
+ newo logs --level <levels> # filter by level: info, warning, error (comma-separated)
63
+ newo logs --type <types> # filter by type: system, operation, call (comma-separated)
64
+ newo logs --flow <idn> --skill <idn> # filter by flow and/or skill
65
+ newo logs --message <text> # search in log messages
66
+ newo logs --follow, -f # tail mode - continuously poll for new logs
67
+ newo logs --json # output logs as JSON array
68
+ newo logs --raw # output each log as single JSON line (for piping)
69
+
70
+ Account & Customer Management:
71
+ newo create-customer <org_name> --email <email> [--tenant <t>] [--phone <p>] [--project <idn>] [--status <temporal|permanent>] # create new NEWO customer
56
72
  newo migrate-account --source <idn> --dest <idn> [--yes] # migrate complete account from source to destination
57
73
  newo verify --source <idn> --dest <idn> # verify migration by comparing entity counts
58
74
  newo create-webhooks [--customer <idn>] # create webhooks from YAML files
@@ -67,6 +83,20 @@ Flags:
67
83
  --confirm # confirm destructive operations without prompting
68
84
  --no-publish # skip automatic flow publishing during push operations
69
85
 
86
+ Selective Sync Flags (NEW):
87
+ --only <resources> # sync only specified resources (comma-separated)
88
+ --exclude <resources> # exclude specified resources from sync
89
+ --all # explicitly sync all resources
90
+ --debounce <ms> # debounce delay for watch mode (default: 1000ms)
91
+ --detailed, -d # show detailed content diff (for diff command)
92
+
93
+ Resources: projects, attributes, integrations, akb, conversations (read-only)
94
+ Examples:
95
+ newo pull --only projects,attributes # pull only projects and attributes
96
+ newo push --exclude integrations # push all except integrations
97
+ newo watch --only projects --debounce 2000 # watch projects with 2s delay
98
+ newo diff --only projects --detailed # show project diffs with content
99
+
70
100
  Environment Variables:
71
101
  NEWO_BASE_URL # NEWO API base URL (default: https://app.newo.ai)
72
102
 
@@ -101,6 +131,21 @@ Usage Examples:
101
131
  newo status # Check for local modifications
102
132
  newo push # Upload changes back to NEWO
103
133
 
134
+ # Watch mode (auto-push on changes):
135
+ newo watch # Watch all files and auto-push changes
136
+ newo watch --only projects # Watch only project files
137
+ newo watch --debounce 2000 # 2-second debounce delay
138
+
139
+ # Diff (compare local vs remote):
140
+ newo diff # Show all differences
141
+ newo diff --only projects # Show only project differences
142
+ newo diff --detailed # Show content-level diffs
143
+
144
+ # Selective sync:
145
+ newo pull --only projects # Pull only projects
146
+ newo push --exclude integrations # Push all except integrations
147
+ newo pull --all # Explicit all resources
148
+
104
149
  # Multi-customer operations:
105
150
  newo pull --customer acme # Pull projects for Acme only
106
151
  newo push --customer globex # Push changes for Globex only
@@ -140,6 +185,21 @@ Usage Examples:
140
185
  newo sandbox "Test query" --verbose # With debug info
141
186
  newo sandbox "Test query" --quiet # For automation/scripts
142
187
 
188
+ # Analytics logs (NEW v3.5.0):
189
+ newo logs # Last 1 hour of logs
190
+ newo logs --hours 24 # Last 24 hours
191
+ newo logs --level warning,error # Only warnings and errors
192
+ newo logs --type call --skill CreateActor # Skill calls for specific skill
193
+ newo logs --flow CACreatorFlow --follow # Tail logs for specific flow
194
+ newo logs --from "2026-01-11T00:00:00Z" --to "2026-01-12T00:00:00Z" # Date range
195
+ newo logs --json --per 100 # Get 100 logs as JSON
196
+ newo logs --message "error" --level error # Search errors with message
197
+
198
+ # Customer creation (NEW v3.5.0):
199
+ newo create-customer "Acme Corp" --email admin@acme.com --tenant acme # Create basic customer
200
+ newo create-customer "Test Co" --email test@test.com --status temporal # Temporal (trial) customer
201
+ newo create-customer "Partner" --email p@p.com --project nac_integration --auto-update # With project template
202
+
143
203
  File Structure:
144
204
  newo_customers/
145
205
  ├── <customer-idn>/