newo 3.7.3 → 3.7.5
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/CHANGELOG.md +35 -2
- package/README.md +71 -2
- package/dist/cli/commands/get-skill.d.ts +3 -0
- package/dist/cli/commands/get-skill.js +72 -0
- package/dist/cli/commands/help.js +29 -0
- package/dist/cli/commands/logs.d.ts +6 -1
- package/dist/cli/commands/logs.js +62 -14
- package/dist/cli/commands/sandbox.d.ts +10 -4
- package/dist/cli/commands/sandbox.js +182 -51
- package/dist/cli/commands/update-skill.d.ts +4 -0
- package/dist/cli/commands/update-skill.js +119 -0
- package/dist/cli.js +8 -0
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.d.ts +37 -0
- package/dist/domain/strategies/sync/V2ProjectSyncStrategy.js +383 -24
- package/dist/sandbox/chat.d.ts +23 -3
- package/dist/sandbox/chat.js +83 -30
- package/dist/sync/remote-skill.d.ts +33 -0
- package/dist/sync/remote-skill.js +52 -0
- package/package.json +1 -1
- package/src/cli/commands/get-skill.ts +84 -0
- package/src/cli/commands/help.ts +29 -0
- package/src/cli/commands/logs.ts +83 -15
- package/src/cli/commands/sandbox.ts +238 -60
- package/src/cli/commands/update-skill.ts +139 -0
- package/src/cli.ts +10 -0
- package/src/domain/strategies/sync/V2ProjectSyncStrategy.ts +530 -26
- package/src/sandbox/chat.ts +106 -29
- package/src/sync/remote-skill.ts +92 -0
|
@@ -1,21 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sandbox Chat Command Handler
|
|
3
3
|
* Supports both single-command and interactive modes
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* npx newo sandbox "Hello" --customer <idn> # Single message mode
|
|
7
|
+
* npx newo sandbox --actor <actor_id> "Follow up" # Continue existing chat
|
|
8
|
+
* npx newo sandbox "ping" --connector vibe_agent # Select specific connector (v3.7.5)
|
|
9
|
+
* npx newo sandbox --list-connectors # Show running sandbox connectors (v3.7.5)
|
|
10
|
+
* npx newo sandbox --file ./msg.txt --actor <id> # Message from file (v3.7.5)
|
|
11
|
+
* cat msg.txt | npx newo sandbox --stdin # Message from stdin (v3.7.5)
|
|
12
|
+
* npx newo sandbox "ping" --timeout 420 # Custom response timeout in seconds (v3.7.5)
|
|
13
|
+
* npx newo sandbox "ping" --json # Machine-readable output (v3.7.5)
|
|
4
14
|
*/
|
|
15
|
+
import fs from 'fs-extra';
|
|
5
16
|
import { makeClient } from '../../api.js';
|
|
6
17
|
import { getValidAccessToken } from '../../auth.js';
|
|
7
18
|
import { selectSingleCustomer } from '../customer-selection.js';
|
|
8
19
|
import { getChatHistory } from '../../api.js';
|
|
9
|
-
import { findSandboxConnector, createChatSession, sendMessage, pollForResponse, extractAgentMessages, formatDebugInfo } from '../../sandbox/chat.js';
|
|
20
|
+
import { findSandboxConnector, listRunningSandboxConnectors, createChatSession, sendMessage, pollForResponse, extractAgentMessages, formatDebugInfo } from '../../sandbox/chat.js';
|
|
21
|
+
const DEFAULT_TIMEOUT_SECONDS = 60;
|
|
22
|
+
/**
|
|
23
|
+
* Normalize an act's external_event_id: the chat-history converter falls back
|
|
24
|
+
* to the placeholder 'chat_history' when the API omits the field.
|
|
25
|
+
*/
|
|
26
|
+
function actEventId(act) {
|
|
27
|
+
if (!act)
|
|
28
|
+
return null;
|
|
29
|
+
const id = act.external_event_id;
|
|
30
|
+
return id && id !== 'chat_history' ? id : null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Read message text from --file, --stdin, or positional argument
|
|
34
|
+
*/
|
|
35
|
+
async function resolveMessage(args) {
|
|
36
|
+
if (args.file) {
|
|
37
|
+
const filePath = String(args.file);
|
|
38
|
+
if (!(await fs.pathExists(filePath))) {
|
|
39
|
+
throw new Error(`Message file not found: ${filePath}`);
|
|
40
|
+
}
|
|
41
|
+
return await fs.readFile(filePath, 'utf8');
|
|
42
|
+
}
|
|
43
|
+
if (args.stdin) {
|
|
44
|
+
const chunks = [];
|
|
45
|
+
for await (const chunk of process.stdin) {
|
|
46
|
+
chunks.push(Buffer.from(chunk));
|
|
47
|
+
}
|
|
48
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
49
|
+
}
|
|
50
|
+
const messageArg = args._[1];
|
|
51
|
+
return messageArg === undefined ? null : String(messageArg);
|
|
52
|
+
}
|
|
10
53
|
/**
|
|
11
54
|
* Handle sandbox command
|
|
12
|
-
* Usage:
|
|
13
|
-
* npx newo sandbox "Hello" --customer <idn> # Single message mode
|
|
14
|
-
* npx newo sandbox --actor <actor_id> "Follow up" # Continue existing chat
|
|
15
|
-
* npx newo sandbox --interactive # Interactive mode (TBD)
|
|
16
55
|
*/
|
|
17
56
|
export async function handleSandboxCommand(customerConfig, args, verbose) {
|
|
18
|
-
const
|
|
57
|
+
const json = Boolean(args.json);
|
|
58
|
+
// --json implies quiet logging: stdout must stay machine-readable
|
|
59
|
+
const quiet = Boolean(args.quiet || args.q) || json;
|
|
19
60
|
// Save original console functions
|
|
20
61
|
const originalConsoleLog = console.log;
|
|
21
62
|
const originalConsoleError = console.error;
|
|
@@ -47,6 +88,13 @@ export async function handleSandboxCommand(customerConfig, args, verbose) {
|
|
|
47
88
|
console.error = originalConsoleError;
|
|
48
89
|
console.warn = originalConsoleWarn;
|
|
49
90
|
}
|
|
91
|
+
const integrationIdn = args.integration ? String(args.integration) : undefined;
|
|
92
|
+
const connectorIdn = args.connector ? String(args.connector) : undefined;
|
|
93
|
+
// List running connectors and exit
|
|
94
|
+
if (args['list-connectors']) {
|
|
95
|
+
await listConnectorsCommand(client, integrationIdn, json);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
50
98
|
// Check for interactive mode
|
|
51
99
|
const interactive = args.interactive || args.i;
|
|
52
100
|
if (interactive) {
|
|
@@ -56,32 +104,42 @@ export async function handleSandboxCommand(customerConfig, args, verbose) {
|
|
|
56
104
|
}
|
|
57
105
|
process.exit(1);
|
|
58
106
|
}
|
|
107
|
+
const timeoutSeconds = args.timeout ? parseFloat(String(args.timeout)) : DEFAULT_TIMEOUT_SECONDS;
|
|
108
|
+
if (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0) {
|
|
109
|
+
if (!quiet)
|
|
110
|
+
console.error(`❌ Invalid --timeout value: ${args.timeout} (expected positive number of seconds)`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
const options = {
|
|
114
|
+
quiet,
|
|
115
|
+
json,
|
|
116
|
+
verbose: quiet ? false : verbose,
|
|
117
|
+
timeoutMs: timeoutSeconds * 1000,
|
|
118
|
+
integrationIdn,
|
|
119
|
+
connectorIdn
|
|
120
|
+
};
|
|
59
121
|
// Check if continuing existing chat
|
|
60
122
|
const actorId = args.actor;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (!messageArg) {
|
|
123
|
+
const message = await resolveMessage(args);
|
|
124
|
+
if (message === null) {
|
|
64
125
|
if (!quiet) {
|
|
65
126
|
console.log('❌ Message is required');
|
|
66
|
-
console.log('Usage: npx newo sandbox "your message" [--actor <id>]');
|
|
67
|
-
console.log(' or: npx newo sandbox --actor <id>
|
|
127
|
+
console.log('Usage: npx newo sandbox "your message" [--actor <id>] [--connector <idn>]');
|
|
128
|
+
console.log(' or: npx newo sandbox --file <path> [--actor <id>]');
|
|
129
|
+
console.log(' or: cat msg.txt | npx newo sandbox --stdin [--actor <id>]');
|
|
68
130
|
}
|
|
69
131
|
process.exit(1);
|
|
70
132
|
}
|
|
71
|
-
// Convert to string (minimist may parse numbers)
|
|
72
|
-
const message = String(messageArg);
|
|
73
133
|
if (message.trim() === '') {
|
|
74
134
|
if (!quiet)
|
|
75
135
|
console.log('❌ Message cannot be empty');
|
|
76
136
|
process.exit(1);
|
|
77
137
|
}
|
|
78
138
|
if (actorId) {
|
|
79
|
-
|
|
80
|
-
await continueExistingChat(client, actorId, message, verbose, quiet, originalConsoleLog, originalConsoleError, originalConsoleWarn);
|
|
139
|
+
await continueExistingChat(client, actorId, message, options, originalConsoleLog);
|
|
81
140
|
}
|
|
82
141
|
else {
|
|
83
|
-
|
|
84
|
-
await startNewChat(client, message, verbose, quiet, originalConsoleLog, originalConsoleError, originalConsoleWarn);
|
|
142
|
+
await startNewChat(client, message, options, originalConsoleLog);
|
|
85
143
|
}
|
|
86
144
|
}
|
|
87
145
|
catch (error) {
|
|
@@ -107,23 +165,78 @@ export async function handleSandboxCommand(customerConfig, args, verbose) {
|
|
|
107
165
|
}
|
|
108
166
|
}
|
|
109
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Print running connectors of the (sandbox) integration
|
|
170
|
+
*/
|
|
171
|
+
async function listConnectorsCommand(client, integrationIdn, asJson) {
|
|
172
|
+
const connectors = await listRunningSandboxConnectors(client, integrationIdn);
|
|
173
|
+
if (asJson) {
|
|
174
|
+
console.log(JSON.stringify(connectors.map(c => ({
|
|
175
|
+
connector_idn: c.connector_idn,
|
|
176
|
+
integration_idn: c.integration_idn,
|
|
177
|
+
title: c.title,
|
|
178
|
+
status: c.status
|
|
179
|
+
})), null, 2));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (connectors.length === 0) {
|
|
183
|
+
console.log(`No running connectors found in integration '${integrationIdn || 'sandbox'}'`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
console.log(`🔌 Running connectors in integration '${integrationIdn || 'sandbox'}':\n`);
|
|
187
|
+
for (const c of connectors) {
|
|
188
|
+
console.log(` ${c.connector_idn}${c.title ? ` (${c.title})` : ''}`);
|
|
189
|
+
}
|
|
190
|
+
console.log(`\n💡 Use: npx newo sandbox "your message" --connector <connector_idn>`);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Build and print the --json result object
|
|
194
|
+
*/
|
|
195
|
+
function printJsonResult(session, acts, userAct, elapsedMs, print) {
|
|
196
|
+
const agentAct = acts.find(a => a.is_agent) || null;
|
|
197
|
+
const jsonResult = {
|
|
198
|
+
actor_id: session.user_actor_id,
|
|
199
|
+
persona_id: session.user_persona_id !== 'unknown' ? session.user_persona_id : null,
|
|
200
|
+
connector_idn: session.connector_idn,
|
|
201
|
+
// external_event_id of the user turn is the correlation key for `newo logs --event-id`
|
|
202
|
+
external_event_id: actEventId(userAct),
|
|
203
|
+
user_external_event_id: actEventId(userAct),
|
|
204
|
+
agent_external_event_id: actEventId(agentAct),
|
|
205
|
+
response: agentAct ? (agentAct.source_text || agentAct.original_text || null) : null,
|
|
206
|
+
elapsed_ms: elapsedMs,
|
|
207
|
+
timed_out: agentAct === null,
|
|
208
|
+
flow_idn: agentAct && agentAct.flow_idn !== 'unknown' ? agentAct.flow_idn : null,
|
|
209
|
+
skill_idn: agentAct && agentAct.skill_idn !== 'unknown' ? agentAct.skill_idn : null,
|
|
210
|
+
session_id: agentAct && agentAct.session_id !== 'unknown' ? agentAct.session_id : null
|
|
211
|
+
};
|
|
212
|
+
print(JSON.stringify(jsonResult, null, 2));
|
|
213
|
+
}
|
|
110
214
|
/**
|
|
111
215
|
* Start a new sandbox chat and send a message
|
|
112
216
|
*/
|
|
113
|
-
async function startNewChat(client, message,
|
|
217
|
+
async function startNewChat(client, message, options, originalConsoleLog) {
|
|
218
|
+
const { quiet, json, verbose, timeoutMs } = options;
|
|
114
219
|
if (!quiet)
|
|
115
220
|
console.log('🔧 Starting new sandbox chat...\n');
|
|
116
|
-
// Find sandbox connector
|
|
117
|
-
const
|
|
221
|
+
// Find sandbox connector (throws with available list when --connector not found)
|
|
222
|
+
const selection = {};
|
|
223
|
+
if (options.integrationIdn)
|
|
224
|
+
selection.integrationIdn = options.integrationIdn;
|
|
225
|
+
if (options.connectorIdn)
|
|
226
|
+
selection.connectorIdn = options.connectorIdn;
|
|
227
|
+
const connector = await findSandboxConnector(client, verbose, selection);
|
|
118
228
|
if (!connector) {
|
|
119
229
|
if (!quiet) {
|
|
120
230
|
console.error('❌ No running sandbox connector found');
|
|
121
231
|
console.error(' Please ensure you have a sandbox connector configured in your NEWO project');
|
|
122
232
|
}
|
|
233
|
+
else if (json) {
|
|
234
|
+
originalConsoleLog(JSON.stringify({ error: 'No running sandbox connector found' }));
|
|
235
|
+
}
|
|
123
236
|
process.exit(1);
|
|
124
237
|
}
|
|
125
238
|
// Create chat session
|
|
126
|
-
const session = await createChatSession(client, connector,
|
|
239
|
+
const session = await createChatSession(client, connector, verbose);
|
|
127
240
|
if (!quiet) {
|
|
128
241
|
console.log(`\n📋 Chat Session Created:`);
|
|
129
242
|
console.log(` Chat ID (actor_id): ${session.user_actor_id}`);
|
|
@@ -132,14 +245,20 @@ async function startNewChat(client, message, verbose, quiet = false, originalCon
|
|
|
132
245
|
console.log(` External ID: ${session.external_id}\n`);
|
|
133
246
|
console.log(`📤 You: ${message}\n`);
|
|
134
247
|
}
|
|
135
|
-
else {
|
|
248
|
+
else if (!json) {
|
|
136
249
|
// In quiet mode, output Chat ID FIRST to stdout
|
|
137
250
|
originalConsoleLog(`CHAT_ID:${session.user_actor_id}`);
|
|
138
251
|
originalConsoleLog(`You: ${message}`);
|
|
139
252
|
}
|
|
140
|
-
const
|
|
253
|
+
const startedAt = Date.now();
|
|
254
|
+
const sentAt = await sendMessage(client, session, message, verbose);
|
|
141
255
|
// Poll for response
|
|
142
|
-
const { acts, agentPersonaId } = await pollForResponse(client, session, sentAt,
|
|
256
|
+
const { acts, agentPersonaId, userAct } = await pollForResponse(client, session, sentAt, verbose, timeoutMs);
|
|
257
|
+
const elapsedMs = Date.now() - startedAt;
|
|
258
|
+
if (json) {
|
|
259
|
+
printJsonResult(session, acts, userAct, elapsedMs, originalConsoleLog);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
143
262
|
if (acts.length === 0) {
|
|
144
263
|
if (!quiet) {
|
|
145
264
|
console.log('⏱️ No response received within timeout period');
|
|
@@ -174,38 +293,40 @@ async function startNewChat(client, message, verbose, quiet = false, originalCon
|
|
|
174
293
|
if (quiet) {
|
|
175
294
|
return; // Exit early, showing only messages
|
|
176
295
|
}
|
|
177
|
-
// Display debug information
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
296
|
+
// Display debug information
|
|
297
|
+
if (verbose) {
|
|
298
|
+
console.log('\n📊 Debug Information:');
|
|
299
|
+
console.log(formatDebugInfo(acts));
|
|
300
|
+
console.log('');
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// Show condensed debug info for single-command mode
|
|
304
|
+
console.log('📊 Debug Summary:');
|
|
305
|
+
const agentActs = acts.filter(a => a.is_agent);
|
|
306
|
+
if (agentActs.length > 0) {
|
|
307
|
+
const lastAct = agentActs[agentActs.length - 1];
|
|
308
|
+
if (lastAct) {
|
|
309
|
+
console.log(` Flow: ${lastAct.flow_idn || 'N/A'}`);
|
|
310
|
+
console.log(` Skill: ${lastAct.skill_idn || 'N/A'}`);
|
|
311
|
+
console.log(` Session: ${lastAct.session_id}`);
|
|
312
|
+
if (actEventId(userAct)) {
|
|
313
|
+
console.log(` Event ID (user turn): ${actEventId(userAct)}`);
|
|
194
314
|
}
|
|
195
|
-
console.log(` Acts Processed: ${acts.length} (${agentActs.length} agent, ${acts.length - agentActs.length} system)`);
|
|
196
315
|
}
|
|
197
|
-
console.log(
|
|
316
|
+
console.log(` Acts Processed: ${acts.length} (${agentActs.length} agent, ${acts.length - agentActs.length} system)`);
|
|
198
317
|
}
|
|
199
|
-
// Show continuation info
|
|
200
|
-
console.log(`💡 To continue this conversation:`);
|
|
201
|
-
console.log(` npx newo sandbox --actor ${session.user_actor_id} "your next message"`);
|
|
202
318
|
console.log('');
|
|
203
319
|
}
|
|
320
|
+
// Show continuation info
|
|
321
|
+
console.log(`💡 To continue this conversation:`);
|
|
322
|
+
console.log(` npx newo sandbox --actor ${session.user_actor_id} "your next message"`);
|
|
323
|
+
console.log('');
|
|
204
324
|
}
|
|
205
325
|
/**
|
|
206
326
|
* Continue an existing sandbox chat
|
|
207
327
|
*/
|
|
208
|
-
async function continueExistingChat(client, actorId, message,
|
|
328
|
+
async function continueExistingChat(client, actorId, message, options, originalConsoleLog) {
|
|
329
|
+
const { quiet, json, verbose, timeoutMs } = options;
|
|
209
330
|
if (!quiet) {
|
|
210
331
|
console.log(`💬 Continuing chat...`);
|
|
211
332
|
console.log(` Chat ID: ${actorId}\n`);
|
|
@@ -232,20 +353,27 @@ async function continueExistingChat(client, actorId, message, verbose, quiet = f
|
|
|
232
353
|
user_actor_id: actorId,
|
|
233
354
|
user_persona_id: 'unknown', // Not needed for continuation
|
|
234
355
|
agent_persona_id: null,
|
|
235
|
-
connector_idn: 'sandbox',
|
|
356
|
+
connector_idn: options.connectorIdn || 'sandbox',
|
|
236
357
|
session_id: null,
|
|
237
358
|
external_id: 'continuation'
|
|
238
359
|
};
|
|
239
360
|
// Send message (use original console in quiet mode)
|
|
240
361
|
if (quiet) {
|
|
241
|
-
|
|
362
|
+
if (!json)
|
|
363
|
+
originalConsoleLog(`You: ${message}`);
|
|
242
364
|
}
|
|
243
365
|
else {
|
|
244
366
|
console.log(`📤 You: ${message}\n`);
|
|
245
367
|
}
|
|
246
|
-
const
|
|
368
|
+
const startedAt = Date.now();
|
|
369
|
+
const sentAt = await sendMessage(client, session, message, verbose);
|
|
247
370
|
// Poll for response using timestamp-based filtering
|
|
248
|
-
const { acts } = await pollForResponse(client, session, sentAt,
|
|
371
|
+
const { acts, userAct } = await pollForResponse(client, session, sentAt, verbose, timeoutMs);
|
|
372
|
+
const elapsedMs = Date.now() - startedAt;
|
|
373
|
+
if (json) {
|
|
374
|
+
printJsonResult(session, acts, userAct, elapsedMs, originalConsoleLog);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
249
377
|
if (acts.length === 0) {
|
|
250
378
|
if (!quiet) {
|
|
251
379
|
console.log('⏱️ No response received within timeout period');
|
|
@@ -292,6 +420,9 @@ async function continueExistingChat(client, actorId, message, verbose, quiet = f
|
|
|
292
420
|
console.log(` Flow: ${lastAct.flow_idn || 'N/A'}`);
|
|
293
421
|
console.log(` Skill: ${lastAct.skill_idn || 'N/A'}`);
|
|
294
422
|
console.log(` Session: ${lastAct.session_id}`);
|
|
423
|
+
if (actEventId(userAct)) {
|
|
424
|
+
console.log(` Event ID (user turn): ${actEventId(userAct)}`);
|
|
425
|
+
}
|
|
295
426
|
}
|
|
296
427
|
console.log(` Acts Processed: ${acts.length} (${agentActs.length} agent, ${acts.length - agentActs.length} user)`);
|
|
297
428
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
2
|
+
export declare function findLocalProjectWorkspace(customerIdn: string, projectIdn: string): Promise<string | null>;
|
|
3
|
+
export declare function handleUpdateSkillCommand(customerConfig: MultiCustomerConfig, args: CliArgs, verbose?: boolean): Promise<void>;
|
|
4
|
+
//# sourceMappingURL=update-skill.d.ts.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Update Skill Command Handler - Point edit of a skill directly on the platform
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* newo update-skill <skill-idn> --project <idn> --agent <idn> --flow <idn> \
|
|
6
|
+
* [--model <provider_idn>/<model_idn>] \
|
|
7
|
+
* [--script <file.nsl|file.guidance>] \
|
|
8
|
+
* [--publish] [--publish-description "<text>"]
|
|
9
|
+
*
|
|
10
|
+
* Unlike `newo push`, this changes exactly one skill without requiring a
|
|
11
|
+
* pulled workspace and without touching any other modified files. Typical
|
|
12
|
+
* use: temporarily switching a skill's model for an A/B test run.
|
|
13
|
+
*/
|
|
14
|
+
import fs from 'fs-extra';
|
|
15
|
+
import { requireSingleCustomer } from '../customer-selection.js';
|
|
16
|
+
import { makeClient, updateSkill, publishFlow } from '../../api.js';
|
|
17
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
18
|
+
import { resolveRemoteSkill, parseModelFlag } from '../../sync/remote-skill.js';
|
|
19
|
+
import { projectDir } from '../../fsutil.js';
|
|
20
|
+
import { v2ProjectDir } from '../../format/paths-v2.js';
|
|
21
|
+
const USAGE = 'Usage: newo update-skill <skill-idn> --project <project-idn> --agent <agent-idn> --flow <flow-idn> [--model <provider>/<model>] [--script <file>] [--publish] [--publish-description "<text>"] [--customer <idn>]';
|
|
22
|
+
export async function findLocalProjectWorkspace(customerIdn, projectIdn) {
|
|
23
|
+
const candidates = [
|
|
24
|
+
projectDir(customerIdn, projectIdn),
|
|
25
|
+
v2ProjectDir(customerIdn, projectIdn)
|
|
26
|
+
];
|
|
27
|
+
for (const candidate of candidates) {
|
|
28
|
+
if (await fs.pathExists(candidate)) {
|
|
29
|
+
return candidate;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
export async function handleUpdateSkillCommand(customerConfig, args, verbose = false) {
|
|
35
|
+
const skillIdn = args._[1];
|
|
36
|
+
const projectIdn = args.project;
|
|
37
|
+
const agentIdn = args.agent;
|
|
38
|
+
const flowIdn = args.flow;
|
|
39
|
+
const modelFlag = args.model;
|
|
40
|
+
const scriptFlag = args.script;
|
|
41
|
+
const shouldPublish = Boolean(args.publish);
|
|
42
|
+
const publishDescription = args['publish-description'];
|
|
43
|
+
if (!skillIdn || !projectIdn || !agentIdn || !flowIdn) {
|
|
44
|
+
console.error('Error: skill IDN, --project, --agent and --flow are required');
|
|
45
|
+
console.error(USAGE);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
if (!modelFlag && !scriptFlag) {
|
|
49
|
+
console.error('Error: nothing to update — pass --model and/or --script');
|
|
50
|
+
console.error(USAGE);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
const newModel = modelFlag ? parseModelFlag(String(modelFlag)) : null;
|
|
54
|
+
let newScript = null;
|
|
55
|
+
if (scriptFlag) {
|
|
56
|
+
const scriptPath = String(scriptFlag);
|
|
57
|
+
if (!(await fs.pathExists(scriptPath))) {
|
|
58
|
+
console.error(`Error: script file not found: ${scriptPath}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
newScript = await fs.readFile(scriptPath, 'utf8');
|
|
62
|
+
}
|
|
63
|
+
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer);
|
|
64
|
+
const token = await getValidAccessToken(selectedCustomer);
|
|
65
|
+
const client = await makeClient(verbose, token);
|
|
66
|
+
if (verbose)
|
|
67
|
+
console.log(`🔍 Resolving skill ${projectIdn}/${agentIdn}/${flowIdn}/${skillIdn}...`);
|
|
68
|
+
const { project, agent, flow, skill } = await resolveRemoteSkill(client, {
|
|
69
|
+
projectIdn,
|
|
70
|
+
agentIdn,
|
|
71
|
+
flowIdn,
|
|
72
|
+
skillIdn
|
|
73
|
+
});
|
|
74
|
+
// Build updated skill object, preserving everything we don't change
|
|
75
|
+
const updatedSkill = {
|
|
76
|
+
...skill,
|
|
77
|
+
...(newModel ? { model: newModel } : {}),
|
|
78
|
+
...(newScript !== null ? { prompt_script: newScript } : {})
|
|
79
|
+
};
|
|
80
|
+
console.log(`✏️ Updating skill: ${project.idn}/${agent.idn}/${flow.idn}/${skill.idn} (${skill.id})`);
|
|
81
|
+
if (newModel) {
|
|
82
|
+
console.log(` Model: ${skill.model.provider_idn}/${skill.model.model_idn} → ${newModel.provider_idn}/${newModel.model_idn}`);
|
|
83
|
+
}
|
|
84
|
+
if (newScript !== null) {
|
|
85
|
+
console.log(` Script: ${(skill.prompt_script || '').length} chars → ${newScript.length} chars (from ${scriptFlag})`);
|
|
86
|
+
}
|
|
87
|
+
await updateSkill(client, updatedSkill);
|
|
88
|
+
console.log('✅ Skill updated (draft)');
|
|
89
|
+
// Warn when a pulled local workspace exists: it now diverges from the platform
|
|
90
|
+
const localProjectDir = await findLocalProjectWorkspace(selectedCustomer.idn, project.idn);
|
|
91
|
+
if (localProjectDir) {
|
|
92
|
+
console.warn(`⚠️ Local workspace exists at ${localProjectDir} and now differs from the platform.`);
|
|
93
|
+
console.warn(` Run 'newo pull' to sync it, or remember to revert this change.`);
|
|
94
|
+
}
|
|
95
|
+
if (shouldPublish) {
|
|
96
|
+
const publishData = {
|
|
97
|
+
version: '1.0',
|
|
98
|
+
description: publishDescription || 'Published via NEWO CLI (update-skill)',
|
|
99
|
+
type: 'public'
|
|
100
|
+
};
|
|
101
|
+
try {
|
|
102
|
+
await publishFlow(client, flow.id, publishData);
|
|
103
|
+
console.log(`🚀 Flow published: ${flow.idn}`);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
const errorMessage = error.response?.data?.message || error.message || 'Unknown error';
|
|
107
|
+
console.error(`❌ Failed to publish flow '${flow.idn}': ${errorMessage}`);
|
|
108
|
+
const errorDetails = error.response?.data?.reasons || error.response?.data?.errors || error.response?.data?.detail;
|
|
109
|
+
if (errorDetails) {
|
|
110
|
+
console.error(` Details: ${JSON.stringify(errorDetails)}`);
|
|
111
|
+
}
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log(`💡 Changes are draft-only. Add --publish to publish flow '${flow.idn}'.`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=update-skill.js.map
|
package/dist/cli.js
CHANGED
|
@@ -21,6 +21,8 @@ import { handleDeleteAgentCommand } from './cli/commands/delete-agent.js';
|
|
|
21
21
|
import { handleCreateFlowCommand } from './cli/commands/create-flow.js';
|
|
22
22
|
import { handleDeleteFlowCommand } from './cli/commands/delete-flow.js';
|
|
23
23
|
import { handleCreateSkillCommand } from './cli/commands/create-skill.js';
|
|
24
|
+
import { handleGetSkillCommand } from './cli/commands/get-skill.js';
|
|
25
|
+
import { handleUpdateSkillCommand } from './cli/commands/update-skill.js';
|
|
24
26
|
import { handleDeleteSkillCommand } from './cli/commands/delete-skill.js';
|
|
25
27
|
import { handleCreateProjectCommand } from './cli/commands/create-project.js';
|
|
26
28
|
import { handleCreateCustomerCommand } from './cli/commands/create-customer.js';
|
|
@@ -161,6 +163,12 @@ async function main() {
|
|
|
161
163
|
case 'create-skill':
|
|
162
164
|
await handleCreateSkillCommand(customerConfig, args, verbose);
|
|
163
165
|
break;
|
|
166
|
+
case 'get-skill':
|
|
167
|
+
await handleGetSkillCommand(customerConfig, args, verbose);
|
|
168
|
+
break;
|
|
169
|
+
case 'update-skill':
|
|
170
|
+
await handleUpdateSkillCommand(customerConfig, args, verbose);
|
|
171
|
+
break;
|
|
164
172
|
case 'delete-skill':
|
|
165
173
|
await handleDeleteSkillCommand(customerConfig, args, verbose);
|
|
166
174
|
break;
|
|
@@ -65,12 +65,48 @@ export declare class V2ProjectSyncStrategy implements ISyncStrategy<ProjectMeta,
|
|
|
65
65
|
* shared syncFlowMetadata routine that calls PATCH/POST/DELETE per child.
|
|
66
66
|
*/
|
|
67
67
|
private pushV2FlowYamlUpdate;
|
|
68
|
+
/**
|
|
69
|
+
* Reconcile inline skill definitions from V2 flow YAML before pushing scripts.
|
|
70
|
+
*
|
|
71
|
+
* V2 keeps skill metadata (model, runner_type, parameters) in the flow YAML,
|
|
72
|
+
* not in a separate skill metadata file. The map only contains the remote IDs
|
|
73
|
+
* from a previous pull, so new local skills must be created before their
|
|
74
|
+
* callers can be published.
|
|
75
|
+
*/
|
|
76
|
+
private syncV2FlowYamlDefinitions;
|
|
77
|
+
private createMissingSkillParameters;
|
|
78
|
+
/**
|
|
79
|
+
* Detect "resource already exists" API errors.
|
|
80
|
+
*
|
|
81
|
+
* Matches only on the precise phrases the platform actually returns
|
|
82
|
+
* ("already exists", "duplicate key"). Loose substrings like "exist"
|
|
83
|
+
* would otherwise sweep up unrelated "does not exist" / "doesn't exist"
|
|
84
|
+
* errors and trigger an incorrect reuse fallback.
|
|
85
|
+
*/
|
|
86
|
+
private isAlreadyExistsApiError;
|
|
87
|
+
private normalizeRunnerType;
|
|
88
|
+
private normalizeParameters;
|
|
89
|
+
/**
|
|
90
|
+
* Fail fast if no model could be resolved for a V2 skill.
|
|
91
|
+
*
|
|
92
|
+
* `buildV2SkillMetadataFromYaml` falls back to empty strings when neither
|
|
93
|
+
* the skill nor the flow declare a model. The platform rejects empty
|
|
94
|
+
* model_idn/provider_idn at creation/update time, but the error it returns
|
|
95
|
+
* is generic — we surface a clearer message before issuing the request.
|
|
96
|
+
*/
|
|
97
|
+
private assertSkillModelResolved;
|
|
98
|
+
private buildV2SkillMetadataFromYaml;
|
|
99
|
+
private skillMetadataDiffers;
|
|
100
|
+
private resolveV2FlowSkillScriptPath;
|
|
68
101
|
/**
|
|
69
102
|
* Push a V2 skill update
|
|
70
103
|
*
|
|
71
104
|
* V2 path: newo_customers/{cust}/{proj}/agents/{agent}/flows/{flow}/skills/{skill}.nsl
|
|
72
105
|
*/
|
|
73
106
|
private pushV2SkillUpdate;
|
|
107
|
+
private normalizePathForComparison;
|
|
108
|
+
private resolveV2SkillTargetForScriptPath;
|
|
109
|
+
private resolveV2SkillTargetFromCanonicalPath;
|
|
74
110
|
/**
|
|
75
111
|
* Push a V2 library skill update
|
|
76
112
|
* Path: .../newo_customers/{cust}/{proj}/libraries/{lib}/skills/{skillFile}
|
|
@@ -81,6 +117,7 @@ export declare class V2ProjectSyncStrategy implements ISyncStrategy<ProjectMeta,
|
|
|
81
117
|
*/
|
|
82
118
|
private publishAllFlows;
|
|
83
119
|
getChanges(customer: CustomerConfig): Promise<ChangeItem<LocalProjectData>[]>;
|
|
120
|
+
private loadLocalV2FlowSkills;
|
|
84
121
|
validate(customer: CustomerConfig, _items: LocalProjectData[]): Promise<ValidationResult>;
|
|
85
122
|
getStatus(customer: CustomerConfig): Promise<StatusSummary>;
|
|
86
123
|
}
|