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
package/dist/sandbox/chat.js
CHANGED
|
@@ -7,7 +7,7 @@ import { listIntegrations, listConnectors, createSandboxPersona, createActor, se
|
|
|
7
7
|
const SANDBOX_INTEGRATION_IDN = 'sandbox';
|
|
8
8
|
const DEFAULT_TIMEZONE = 'America/Los_Angeles';
|
|
9
9
|
const POLL_INTERVAL_MS = 1000; // 1 second
|
|
10
|
-
const
|
|
10
|
+
const DEFAULT_TIMEOUT_MS = 60_000; // Default max wait for agent response
|
|
11
11
|
/**
|
|
12
12
|
* Generate a random external ID for chat session
|
|
13
13
|
*/
|
|
@@ -22,39 +22,67 @@ function generatePersonaName() {
|
|
|
22
22
|
return `newo-cli-${guid}`;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
25
|
+
* List running connectors of an integration (default: sandbox).
|
|
26
|
+
* Used by `newo sandbox --list-connectors` and connector selection.
|
|
26
27
|
*/
|
|
27
|
-
export async function
|
|
28
|
-
if (verbose)
|
|
29
|
-
console.log('š Searching for sandbox integration...');
|
|
30
|
-
// First, get all integrations to find the sandbox integration
|
|
28
|
+
export async function listRunningSandboxConnectors(client, integrationIdn = SANDBOX_INTEGRATION_IDN) {
|
|
31
29
|
const integrations = await listIntegrations(client);
|
|
32
|
-
const
|
|
33
|
-
if (!
|
|
30
|
+
const integration = integrations.find(i => i.idn === integrationIdn);
|
|
31
|
+
if (!integration) {
|
|
32
|
+
throw new Error(`Integration '${integrationIdn}' not found. Available integrations: ${integrations.map(i => i.idn).join(', ') || '(none)'}`);
|
|
33
|
+
}
|
|
34
|
+
const connectors = await listConnectors(client, integration.id);
|
|
35
|
+
return connectors.filter(c => c.status === 'running');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Find a sandbox connector from the customer's connectors list.
|
|
39
|
+
*
|
|
40
|
+
* Without options, preserves legacy behavior: first running connector of the
|
|
41
|
+
* 'sandbox' integration. With options.connectorIdn, selects that exact
|
|
42
|
+
* connector and throws a descriptive error (listing available connectors)
|
|
43
|
+
* when it is not found or not running.
|
|
44
|
+
*/
|
|
45
|
+
export async function findSandboxConnector(client, verbose = false, options = {}) {
|
|
46
|
+
const integrationIdn = options.integrationIdn || SANDBOX_INTEGRATION_IDN;
|
|
47
|
+
if (verbose)
|
|
48
|
+
console.log(`š Searching for ${integrationIdn} integration...`);
|
|
49
|
+
let runningConnectors;
|
|
50
|
+
try {
|
|
51
|
+
runningConnectors = await listRunningSandboxConnectors(client, integrationIdn);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (options.connectorIdn)
|
|
55
|
+
throw error;
|
|
34
56
|
if (verbose)
|
|
35
|
-
console.log(
|
|
57
|
+
console.log(`ā ${error instanceof Error ? error.message : String(error)}`);
|
|
36
58
|
return null;
|
|
37
59
|
}
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
console.log('š Searching for sandbox connectors...');
|
|
43
|
-
const connectors = await listConnectors(client, sandboxIntegration.id);
|
|
44
|
-
const sandboxConnectors = connectors.filter(c => c.status === 'running');
|
|
45
|
-
if (sandboxConnectors.length === 0) {
|
|
60
|
+
if (runningConnectors.length === 0) {
|
|
61
|
+
if (options.connectorIdn) {
|
|
62
|
+
throw new Error(`No running connectors found in integration '${integrationIdn}'`);
|
|
63
|
+
}
|
|
46
64
|
if (verbose)
|
|
47
|
-
console.log(
|
|
65
|
+
console.log(`ā No running ${integrationIdn} connectors found`);
|
|
48
66
|
return null;
|
|
49
67
|
}
|
|
68
|
+
if (options.connectorIdn) {
|
|
69
|
+
const match = runningConnectors.find(c => c.connector_idn === options.connectorIdn);
|
|
70
|
+
if (!match) {
|
|
71
|
+
const available = runningConnectors.map(c => c.connector_idn).join(', ');
|
|
72
|
+
throw new Error(`Connector '${options.connectorIdn}' not found among running connectors of integration '${integrationIdn}'. Available: ${available}`);
|
|
73
|
+
}
|
|
74
|
+
if (verbose)
|
|
75
|
+
console.log(`ā Using connector: ${match.connector_idn}`);
|
|
76
|
+
return match;
|
|
77
|
+
}
|
|
50
78
|
if (verbose) {
|
|
51
|
-
console.log(`ā Found ${
|
|
52
|
-
const firstConnector =
|
|
79
|
+
console.log(`ā Found ${runningConnectors.length} running ${integrationIdn} connector(s)`);
|
|
80
|
+
const firstConnector = runningConnectors[0];
|
|
53
81
|
if (firstConnector) {
|
|
54
82
|
console.log(` Using: ${firstConnector.connector_idn}`);
|
|
55
83
|
}
|
|
56
84
|
}
|
|
57
|
-
return
|
|
85
|
+
return runningConnectors[0] || null;
|
|
58
86
|
}
|
|
59
87
|
/**
|
|
60
88
|
* Create a new sandbox chat session
|
|
@@ -77,7 +105,7 @@ export async function createChatSession(client, connector, verbose = false) {
|
|
|
77
105
|
const actorResponse = await createActor(client, personaResponse.id, {
|
|
78
106
|
name: personaName,
|
|
79
107
|
external_id: externalId,
|
|
80
|
-
integration_idn: SANDBOX_INTEGRATION_IDN,
|
|
108
|
+
integration_idn: connector.integration_idn || SANDBOX_INTEGRATION_IDN,
|
|
81
109
|
connector_idn: connector.connector_idn,
|
|
82
110
|
time_zone_identifier: DEFAULT_TIMEZONE
|
|
83
111
|
});
|
|
@@ -97,8 +125,10 @@ export async function createChatSession(client, connector, verbose = false) {
|
|
|
97
125
|
* Returns the timestamp when message was sent (for filtering responses)
|
|
98
126
|
*/
|
|
99
127
|
export async function sendMessage(client, session, text, verbose = false) {
|
|
100
|
-
if (verbose)
|
|
101
|
-
|
|
128
|
+
if (verbose) {
|
|
129
|
+
const preview = text.length > 200 ? `${text.slice(0, 200)}⦠(${text.length} chars)` : text;
|
|
130
|
+
console.log(`š¬ Sending message: "${preview}"`);
|
|
131
|
+
}
|
|
102
132
|
const sentAt = new Date();
|
|
103
133
|
await sendChatMessage(client, session.user_actor_id, {
|
|
104
134
|
text,
|
|
@@ -108,21 +138,33 @@ export async function sendMessage(client, session, text, verbose = false) {
|
|
|
108
138
|
console.log('ā Message sent');
|
|
109
139
|
return sentAt;
|
|
110
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Parse an act datetime that may lack timezone info (assume UTC)
|
|
143
|
+
*/
|
|
144
|
+
function parseActDatetimeMs(datetime) {
|
|
145
|
+
let d = datetime;
|
|
146
|
+
if (!d.endsWith('Z') && !d.includes('+') && !d.includes('-', 10)) {
|
|
147
|
+
d = d + 'Z';
|
|
148
|
+
}
|
|
149
|
+
return new Date(d).getTime();
|
|
150
|
+
}
|
|
111
151
|
/**
|
|
112
152
|
* Poll for new conversation acts (messages and debug info)
|
|
113
153
|
* Continues polling until we get an agent response, not just any new message
|
|
114
154
|
*/
|
|
115
|
-
export async function pollForResponse(client, session, messageSentAt = null, verbose = false) {
|
|
155
|
+
export async function pollForResponse(client, session, messageSentAt = null, verbose = false, timeoutMs = DEFAULT_TIMEOUT_MS) {
|
|
116
156
|
let attempts = 0;
|
|
117
157
|
let agentPersonaId = session.agent_persona_id;
|
|
158
|
+
let userAct = null;
|
|
159
|
+
const maxPollAttempts = Math.max(1, Math.ceil(timeoutMs / POLL_INTERVAL_MS));
|
|
118
160
|
if (verbose)
|
|
119
|
-
console.log(
|
|
161
|
+
console.log(`ā³ Waiting for agent response (timeout: ${Math.round(timeoutMs / 1000)}s)...`);
|
|
120
162
|
// Add small delay before first poll to allow message to be processed
|
|
121
163
|
await new Promise(resolve => setTimeout(resolve, 500));
|
|
122
|
-
while (attempts <
|
|
164
|
+
while (attempts < maxPollAttempts) {
|
|
123
165
|
try {
|
|
124
166
|
if (verbose && attempts % 5 === 0) {
|
|
125
|
-
console.log(` [Poll attempt ${attempts + 1}/${
|
|
167
|
+
console.log(` [Poll attempt ${attempts + 1}/${maxPollAttempts}] Checking for messages...`);
|
|
126
168
|
}
|
|
127
169
|
// Use Chat History API instead of acts API (doesn't require account_id)
|
|
128
170
|
const response = await getChatHistory(client, {
|
|
@@ -172,6 +214,17 @@ export async function pollForResponse(client, session, messageSentAt = null, ver
|
|
|
172
214
|
console.log(`ā Extracted agent_persona_id: ${agentPersonaId}`);
|
|
173
215
|
}
|
|
174
216
|
}
|
|
217
|
+
// Track the user act of our sent message (newest non-agent act at/after sentAt).
|
|
218
|
+
// Its external_event_id is the correlation key for `newo logs --event-id`.
|
|
219
|
+
for (const act of convertedActs) {
|
|
220
|
+
if (act.is_agent)
|
|
221
|
+
continue;
|
|
222
|
+
if (messageSentAt && parseActDatetimeMs(act.datetime) - messageSentAt.getTime() <= -100)
|
|
223
|
+
continue;
|
|
224
|
+
if (!userAct || parseActDatetimeMs(act.datetime) >= parseActDatetimeMs(userAct.datetime)) {
|
|
225
|
+
userAct = act;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
175
228
|
// Filter for agent messages that came AFTER our message was sent
|
|
176
229
|
const agentMessages = convertedActs.filter(act => {
|
|
177
230
|
if (!act.is_agent)
|
|
@@ -208,7 +261,7 @@ export async function pollForResponse(client, session, messageSentAt = null, ver
|
|
|
208
261
|
// Return ONLY the single newest agent message (first one, since API returns newest first)
|
|
209
262
|
const latestAgentMessage = agentMessages[0];
|
|
210
263
|
if (latestAgentMessage) {
|
|
211
|
-
return { acts: [latestAgentMessage], agentPersonaId };
|
|
264
|
+
return { acts: [latestAgentMessage], agentPersonaId, userAct };
|
|
212
265
|
}
|
|
213
266
|
}
|
|
214
267
|
else if (verbose && attempts % 10 === 0) {
|
|
@@ -227,7 +280,7 @@ export async function pollForResponse(client, session, messageSentAt = null, ver
|
|
|
227
280
|
}
|
|
228
281
|
if (verbose)
|
|
229
282
|
console.log('ā±ļø Timeout waiting for response');
|
|
230
|
-
return { acts: [], agentPersonaId };
|
|
283
|
+
return { acts: [], agentPersonaId, userAct };
|
|
231
284
|
}
|
|
232
285
|
/**
|
|
233
286
|
* Extract agent messages from acts
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote skill resolution by IDN path (project/agent/flow/skill).
|
|
3
|
+
*
|
|
4
|
+
* Used by `newo get-skill` and `newo update-skill` to address a single skill
|
|
5
|
+
* on the platform without requiring a pulled local workspace.
|
|
6
|
+
*/
|
|
7
|
+
import type { AxiosInstance } from 'axios';
|
|
8
|
+
import type { ProjectMeta, Agent, Flow, Skill } from '../types.js';
|
|
9
|
+
export interface RemoteSkillPath {
|
|
10
|
+
projectIdn: string;
|
|
11
|
+
agentIdn: string;
|
|
12
|
+
flowIdn: string;
|
|
13
|
+
skillIdn: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ResolvedRemoteSkill {
|
|
16
|
+
project: ProjectMeta;
|
|
17
|
+
agent: Agent;
|
|
18
|
+
flow: Flow;
|
|
19
|
+
skill: Skill;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a skill on the platform by its IDN path.
|
|
23
|
+
* Throws descriptive errors listing available IDNs at the failing level.
|
|
24
|
+
*/
|
|
25
|
+
export declare function resolveRemoteSkill(client: AxiosInstance, path: RemoteSkillPath): Promise<ResolvedRemoteSkill>;
|
|
26
|
+
/**
|
|
27
|
+
* Parse a `--model provider_idn/model_idn` flag value
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseModelFlag(value: string): {
|
|
30
|
+
provider_idn: string;
|
|
31
|
+
model_idn: string;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=remote-skill.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { listProjects, listAgents, listFlowSkills, getSkill } from '../api.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a skill on the platform by its IDN path.
|
|
4
|
+
* Throws descriptive errors listing available IDNs at the failing level.
|
|
5
|
+
*/
|
|
6
|
+
export async function resolveRemoteSkill(client, path) {
|
|
7
|
+
const projects = await listProjects(client);
|
|
8
|
+
const project = projects.find(p => p.idn === path.projectIdn);
|
|
9
|
+
if (!project) {
|
|
10
|
+
throw new Error(`Project '${path.projectIdn}' not found. Available projects: ${projects.map(p => p.idn).join(', ') || '(none)'}`);
|
|
11
|
+
}
|
|
12
|
+
const agents = await listAgents(client, project.id);
|
|
13
|
+
const agent = agents.find(a => a.idn === path.agentIdn);
|
|
14
|
+
if (!agent) {
|
|
15
|
+
throw new Error(`Agent '${path.agentIdn}' not found in project '${path.projectIdn}'. Available agents: ${agents.map(a => a.idn).join(', ') || '(none)'}`);
|
|
16
|
+
}
|
|
17
|
+
const flows = agent.flows || [];
|
|
18
|
+
const flow = flows.find(f => f.idn === path.flowIdn);
|
|
19
|
+
if (!flow) {
|
|
20
|
+
throw new Error(`Flow '${path.flowIdn}' not found in agent '${path.agentIdn}'. Available flows: ${flows.map(f => f.idn).join(', ') || '(none)'}`);
|
|
21
|
+
}
|
|
22
|
+
const skills = await listFlowSkills(client, flow.id);
|
|
23
|
+
const skillStub = skills.find(s => s.idn === path.skillIdn);
|
|
24
|
+
if (!skillStub) {
|
|
25
|
+
throw new Error(`Skill '${path.skillIdn}' not found in flow '${path.flowIdn}'. Available skills: ${skills.map(s => s.idn).join(', ') || '(none)'}`);
|
|
26
|
+
}
|
|
27
|
+
// The flow-skills list endpoint already returns full skills including
|
|
28
|
+
// prompt_script (pull relies on this). Only fall back to the by-id
|
|
29
|
+
// endpoint when prompt_script is absent ā and tolerate a 404 there,
|
|
30
|
+
// since /api/v1/designer/skills/{id} is not available on all accounts.
|
|
31
|
+
let skill = skillStub;
|
|
32
|
+
if (skill.prompt_script === undefined || skill.prompt_script === null) {
|
|
33
|
+
try {
|
|
34
|
+
skill = await getSkill(client, skillStub.id);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
skill = skillStub;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { project, agent, flow, skill };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Parse a `--model provider_idn/model_idn` flag value
|
|
44
|
+
*/
|
|
45
|
+
export function parseModelFlag(value) {
|
|
46
|
+
const parts = value.split('/');
|
|
47
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
48
|
+
throw new Error(`Invalid --model value '${value}'. Expected format: <provider_idn>/<model_idn> (e.g. openai/gpt4o)`);
|
|
49
|
+
}
|
|
50
|
+
return { provider_idn: parts[0], model_idn: parts[1] };
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=remote-skill.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "newo",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.5",
|
|
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": {
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get Skill Command Handler - Inspect a skill's live state on the platform
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* newo get-skill <skill-idn> --project <idn> --agent <idn> --flow <idn> [--json]
|
|
6
|
+
*
|
|
7
|
+
* Shows the skill as it currently lives on the platform (model, runner_type,
|
|
8
|
+
* parameters, prompt_script) without requiring a pulled local workspace.
|
|
9
|
+
*/
|
|
10
|
+
import { requireSingleCustomer } from '../customer-selection.js';
|
|
11
|
+
import { makeClient } from '../../api.js';
|
|
12
|
+
import { getValidAccessToken } from '../../auth.js';
|
|
13
|
+
import { resolveRemoteSkill } from '../../sync/remote-skill.js';
|
|
14
|
+
import type { MultiCustomerConfig, CliArgs } from '../../types.js';
|
|
15
|
+
|
|
16
|
+
const USAGE = 'Usage: newo get-skill <skill-idn> --project <project-idn> --agent <agent-idn> --flow <flow-idn> [--json] [--customer <idn>]';
|
|
17
|
+
|
|
18
|
+
export async function handleGetSkillCommand(
|
|
19
|
+
customerConfig: MultiCustomerConfig,
|
|
20
|
+
args: CliArgs,
|
|
21
|
+
verbose: boolean = false
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
const skillIdn = args._[1] as string | undefined;
|
|
24
|
+
const projectIdn = args.project as string | undefined;
|
|
25
|
+
const agentIdn = args.agent as string | undefined;
|
|
26
|
+
const flowIdn = args.flow as string | undefined;
|
|
27
|
+
const asJson = Boolean(args.json);
|
|
28
|
+
|
|
29
|
+
if (!skillIdn || !projectIdn || !agentIdn || !flowIdn) {
|
|
30
|
+
console.error('Error: skill IDN, --project, --agent and --flow are required');
|
|
31
|
+
console.error(USAGE);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer as string | undefined);
|
|
36
|
+
|
|
37
|
+
if (asJson) {
|
|
38
|
+
process.env.NEWO_QUIET_MODE = 'true'; // keep stdout machine-readable for piping
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const token = await getValidAccessToken(selectedCustomer);
|
|
42
|
+
const client = await makeClient(verbose, token);
|
|
43
|
+
|
|
44
|
+
if (verbose) console.log(`š Resolving skill ${projectIdn}/${agentIdn}/${flowIdn}/${skillIdn}...`);
|
|
45
|
+
|
|
46
|
+
const { project, agent, flow, skill } = await resolveRemoteSkill(client, {
|
|
47
|
+
projectIdn,
|
|
48
|
+
agentIdn,
|
|
49
|
+
flowIdn,
|
|
50
|
+
skillIdn
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (asJson) {
|
|
54
|
+
console.log(JSON.stringify({
|
|
55
|
+
project_idn: project.idn,
|
|
56
|
+
agent_idn: agent.idn,
|
|
57
|
+
flow_idn: flow.idn,
|
|
58
|
+
id: skill.id,
|
|
59
|
+
idn: skill.idn,
|
|
60
|
+
title: skill.title,
|
|
61
|
+
runner_type: skill.runner_type,
|
|
62
|
+
model: skill.model,
|
|
63
|
+
parameters: skill.parameters,
|
|
64
|
+
prompt_script: skill.prompt_script ?? null
|
|
65
|
+
}, null, 2));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log(`š Skill: ${project.idn}/${agent.idn}/${flow.idn}/${skill.idn}`);
|
|
70
|
+
console.log(` ID: ${skill.id}`);
|
|
71
|
+
console.log(` Title: ${skill.title}`);
|
|
72
|
+
console.log(` Runner type: ${skill.runner_type}`);
|
|
73
|
+
console.log(` Model: ${skill.model.provider_idn}/${skill.model.model_idn}`);
|
|
74
|
+
if (skill.parameters.length > 0) {
|
|
75
|
+
console.log(` Parameters:`);
|
|
76
|
+
for (const param of skill.parameters) {
|
|
77
|
+
console.log(` ${param.name}${param.default_value !== undefined ? ` = ${JSON.stringify(param.default_value)}` : ''}`);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
console.log(` Parameters: (none)`);
|
|
81
|
+
}
|
|
82
|
+
console.log(`\n--- prompt_script (${(skill.prompt_script || '').length} chars) ---`);
|
|
83
|
+
console.log(skill.prompt_script || '(empty)');
|
|
84
|
+
}
|
package/src/cli/commands/help.ts
CHANGED
|
@@ -17,6 +17,11 @@ Core Commands:
|
|
|
17
17
|
newo conversations [--customer <idn>] [--all] # download user conversations -> conversations.yaml
|
|
18
18
|
newo sandbox "<message>" [--customer <idn>] # test agent in sandbox - single message mode
|
|
19
19
|
newo sandbox --actor <id> "message" # continue existing sandbox conversation
|
|
20
|
+
newo sandbox --list-connectors # list running sandbox connectors (NEW v3.7.5)
|
|
21
|
+
newo sandbox "<msg>" --connector <idn> # chat through a specific connector (NEW v3.7.5)
|
|
22
|
+
newo sandbox --file <path> | --stdin # send long messages from file/stdin (NEW v3.7.5)
|
|
23
|
+
newo get-skill <idn> --project <p> --agent <a> --flow <f> [--json] # inspect live skill on platform (NEW v3.7.5)
|
|
24
|
+
newo update-skill <idn> --project <p> --agent <a> --flow <f> [--model <prov>/<model>] [--script <file>] [--publish] # point-edit skill (NEW v3.7.5)
|
|
20
25
|
newo pull-attributes [--customer <idn>] # download customer + project attributes
|
|
21
26
|
newo list-customers # list available customers
|
|
22
27
|
newo meta [--customer <idn>] # get project metadata (debug)
|
|
@@ -77,6 +82,8 @@ Analytics & Monitoring (NEW):
|
|
|
77
82
|
newo logs --type <types> # filter by type: system, operation, call (comma-separated)
|
|
78
83
|
newo logs --flow <idn> --skill <idn> # filter by flow and/or skill
|
|
79
84
|
newo logs --message <text> # search in log messages
|
|
85
|
+
newo logs --name <ActionName> # filter by action name in data.name, e.g. Gen, GetMemory (NEW v3.7.5)
|
|
86
|
+
newo logs --event-id <uuid> # filter by external event ID (correlate with sandbox --json)
|
|
80
87
|
newo logs --follow, -f # tail mode - continuously poll for new logs
|
|
81
88
|
newo logs --json # output logs as JSON array
|
|
82
89
|
newo logs --raw # output each log as single JSON line (for piping)
|
|
@@ -95,6 +102,12 @@ Flags:
|
|
|
95
102
|
--verbose, -v # enable detailed logging and progress information
|
|
96
103
|
--quiet, -q # minimal output for automation (sandbox only)
|
|
97
104
|
--actor <id> # continue existing sandbox chat with actor/chat ID
|
|
105
|
+
--connector <idn> # sandbox: select connector by connector_idn (default: first running)
|
|
106
|
+
--integration <idn> # sandbox: integration to search connectors in (default: sandbox)
|
|
107
|
+
--file <path> # sandbox: read message text from file
|
|
108
|
+
--stdin # sandbox: read message text from stdin
|
|
109
|
+
--timeout <seconds> # sandbox: max wait for agent response (default: 60)
|
|
110
|
+
--json # sandbox: machine-readable output incl. external_event_id (user+agent turns)
|
|
98
111
|
--confirm # confirm destructive operations without prompting
|
|
99
112
|
--no-publish # skip automatic flow publishing during push operations
|
|
100
113
|
--output, -o <file> # output file path (for export command)
|
|
@@ -202,6 +215,22 @@ Usage Examples:
|
|
|
202
215
|
newo sandbox "Test query" --verbose # With debug info
|
|
203
216
|
newo sandbox "Test query" --quiet # For automation/scripts
|
|
204
217
|
|
|
218
|
+
# Sandbox connector selection + automation (NEW v3.7.5):
|
|
219
|
+
newo sandbox --list-connectors # Show running sandbox connectors
|
|
220
|
+
newo sandbox "ping" --connector vibe_agent # Chat through specific connector
|
|
221
|
+
newo sandbox --file ./chunk1.txt --actor abc123... --json # Long message from file, JSON output
|
|
222
|
+
cat msg.txt | newo sandbox --stdin --timeout 420 --json # From stdin with 7-minute timeout
|
|
223
|
+
# --json output: {actor_id, persona_id, external_event_id, user_external_event_id,
|
|
224
|
+
# agent_external_event_id, response, elapsed_ms, timed_out, ...}
|
|
225
|
+
# Correlate a turn with its logs: newo logs --event-id <external_event_id> --json
|
|
226
|
+
|
|
227
|
+
# Live skill inspection / point edits (NEW v3.7.5):
|
|
228
|
+
newo get-skill structured_generation --project vibe --agent VibeAgent --flow VibeFlow # View live state
|
|
229
|
+
newo get-skill structured_generation --project vibe --agent VibeAgent --flow VibeFlow --json # As JSON
|
|
230
|
+
newo update-skill structured_generation --project vibe --agent VibeAgent --flow VibeFlow \\
|
|
231
|
+
--model openai/gpt54 --publish # Switch model and publish
|
|
232
|
+
newo update-skill my_skill --project p --agent a --flow f --script ./patched.nsl --publish # Replace script
|
|
233
|
+
|
|
205
234
|
# Analytics logs (NEW v3.5.0):
|
|
206
235
|
newo logs # Last 1 hour of logs
|
|
207
236
|
newo logs --hours 24 # Last 24 hours
|
package/src/cli/commands/logs.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { AxiosInstance } from 'axios';
|
|
17
|
-
import type { MultiCustomerConfig, LogEntry, LogLevel, LogType, LogsQueryParams, CliArgs } from '../../types.js';
|
|
17
|
+
import type { MultiCustomerConfig, LogEntry, LogLevel, LogType, LogsQueryParams, LogsResponse, CliArgs } from '../../types.js';
|
|
18
18
|
import { makeClient, getLogs } from '../../api.js';
|
|
19
19
|
import { getValidAccessToken } from '../../auth.js';
|
|
20
20
|
import { requireSingleCustomer } from '../customer-selection.js';
|
|
@@ -33,6 +33,8 @@ const colors = {
|
|
|
33
33
|
gray: '\x1b[90m'
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
+
type GetLogsFn = (client: AxiosInstance, params: LogsQueryParams) => Promise<LogsResponse>;
|
|
37
|
+
|
|
36
38
|
function getLevelColor(level: LogLevel): string {
|
|
37
39
|
switch (level) {
|
|
38
40
|
case 'error': return colors.red;
|
|
@@ -106,7 +108,13 @@ export async function handleLogsCommand(
|
|
|
106
108
|
// Select customer
|
|
107
109
|
const selectedCustomer = requireSingleCustomer(customerConfig, args.customer as string | undefined);
|
|
108
110
|
|
|
109
|
-
|
|
111
|
+
// Keep stdout machine-readable when JSON output is requested (for piping to jq etc.)
|
|
112
|
+
const machineOutput = Boolean(args.json || args.raw);
|
|
113
|
+
if (machineOutput) {
|
|
114
|
+
process.env.NEWO_QUIET_MODE = 'true'; // suppress auth logging on stdout
|
|
115
|
+
} else {
|
|
116
|
+
console.log(`š Fetching logs for ${selectedCustomer.idn}...`);
|
|
117
|
+
}
|
|
110
118
|
|
|
111
119
|
// Get access token and create client
|
|
112
120
|
const token = await getValidAccessToken(selectedCustomer);
|
|
@@ -164,29 +172,84 @@ export async function handleLogsCommand(
|
|
|
164
172
|
const follow = Boolean(args.follow || args.f);
|
|
165
173
|
const asJson = Boolean(args.json);
|
|
166
174
|
const raw = Boolean(args.raw);
|
|
175
|
+
// --name filters by data.name (e.g. action name like Gen or GetMemory).
|
|
176
|
+
// The API has no such query param, so it is applied client-side.
|
|
177
|
+
const nameFilter = args.name ? String(args.name) : null;
|
|
167
178
|
|
|
168
179
|
if (follow) {
|
|
169
|
-
await tailLogs(client, params, asJson);
|
|
180
|
+
await tailLogs(client, params, asJson, nameFilter);
|
|
170
181
|
} else {
|
|
171
|
-
await fetchAndDisplayLogs(client, params, asJson, raw);
|
|
182
|
+
await fetchAndDisplayLogs(client, params, asJson, raw, nameFilter);
|
|
172
183
|
}
|
|
173
184
|
}
|
|
174
185
|
|
|
175
|
-
|
|
186
|
+
function filterByName(logs: readonly LogEntry[], nameFilter: string | null): LogEntry[] {
|
|
187
|
+
if (!nameFilter) return [...logs];
|
|
188
|
+
return logs.filter(log => log.data['name'] === nameFilter);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export async function collectLogsForDisplay(
|
|
192
|
+
client: AxiosInstance,
|
|
193
|
+
params: LogsQueryParams,
|
|
194
|
+
nameFilter: string | null = null,
|
|
195
|
+
getLogsFn: GetLogsFn = getLogs
|
|
196
|
+
): Promise<LogEntry[]> {
|
|
197
|
+
if (!nameFilter) {
|
|
198
|
+
const response = await getLogsFn(client, params);
|
|
199
|
+
return [...response.items];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const pageSize = Number.isFinite(params.per) && params.per && params.per > 0 ? params.per : 50;
|
|
203
|
+
let page = Number.isFinite(params.page) && params.page && params.page > 0 ? params.page : 1;
|
|
204
|
+
const logs: LogEntry[] = [];
|
|
205
|
+
|
|
206
|
+
while (true) {
|
|
207
|
+
const response = await getLogsFn(client, {
|
|
208
|
+
...params,
|
|
209
|
+
page,
|
|
210
|
+
per: pageSize
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
logs.push(...filterByName(response.items, nameFilter));
|
|
214
|
+
|
|
215
|
+
if (response.items.length < pageSize) {
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
page++;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return logs;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export async function fetchAndDisplayLogs(
|
|
176
226
|
client: AxiosInstance,
|
|
177
227
|
params: LogsQueryParams,
|
|
178
228
|
asJson: boolean,
|
|
179
|
-
raw: boolean
|
|
229
|
+
raw: boolean,
|
|
230
|
+
nameFilter: string | null = null,
|
|
231
|
+
getLogsFn: GetLogsFn = getLogs
|
|
180
232
|
): Promise<void> {
|
|
181
233
|
try {
|
|
182
|
-
const
|
|
183
|
-
const logs = response.items;
|
|
234
|
+
const logs = await collectLogsForDisplay(client, params, nameFilter, getLogsFn);
|
|
184
235
|
|
|
185
236
|
if (asJson) {
|
|
186
237
|
console.log(JSON.stringify(logs, null, 2));
|
|
187
238
|
return;
|
|
188
239
|
}
|
|
189
240
|
|
|
241
|
+
// --raw is a machine-readable JSONL contract: stdout must contain only
|
|
242
|
+
// one JSON object per log line, with no banners or empty-result text.
|
|
243
|
+
if (raw) {
|
|
244
|
+
const sortedLogs = [...logs].sort((a, b) =>
|
|
245
|
+
new Date(a.datetime).getTime() - new Date(b.datetime).getTime()
|
|
246
|
+
);
|
|
247
|
+
for (const log of sortedLogs) {
|
|
248
|
+
console.log(JSON.stringify(log));
|
|
249
|
+
}
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
190
253
|
if (logs.length === 0) {
|
|
191
254
|
console.log('\nNo logs found for the specified criteria.');
|
|
192
255
|
return;
|
|
@@ -203,11 +266,7 @@ async function fetchAndDisplayLogs(
|
|
|
203
266
|
const useColors = process.stdout.isTTY !== false;
|
|
204
267
|
|
|
205
268
|
for (const log of sortedLogs) {
|
|
206
|
-
|
|
207
|
-
console.log(JSON.stringify(log));
|
|
208
|
-
} else {
|
|
209
|
-
console.log(formatLogEntry(log, useColors));
|
|
210
|
-
}
|
|
269
|
+
console.log(formatLogEntry(log, useColors));
|
|
211
270
|
}
|
|
212
271
|
|
|
213
272
|
console.log(`\nā
Displayed ${logs.length} log entries`);
|
|
@@ -220,7 +279,8 @@ async function fetchAndDisplayLogs(
|
|
|
220
279
|
async function tailLogs(
|
|
221
280
|
client: AxiosInstance,
|
|
222
281
|
params: LogsQueryParams,
|
|
223
|
-
asJson: boolean
|
|
282
|
+
asJson: boolean,
|
|
283
|
+
nameFilter: string | null = null
|
|
224
284
|
): Promise<void> {
|
|
225
285
|
console.log('š Watching for new logs (Ctrl+C to stop)...\n');
|
|
226
286
|
|
|
@@ -241,7 +301,7 @@ async function tailLogs(
|
|
|
241
301
|
};
|
|
242
302
|
|
|
243
303
|
const response = await getLogs(client, pollParams);
|
|
244
|
-
const logs = response.items;
|
|
304
|
+
const logs = filterByName(response.items, nameFilter);
|
|
245
305
|
|
|
246
306
|
// Filter out already seen logs and sort by time
|
|
247
307
|
const newLogs = logs
|
|
@@ -304,6 +364,7 @@ Filter Options:
|
|
|
304
364
|
--flow <idn> Filter by flow IDN
|
|
305
365
|
--skill <idn> Filter by skill IDN
|
|
306
366
|
--message <text> Search in log messages
|
|
367
|
+
--name <ActionName> Filter by action name in data.name, e.g. Gen, GetMemory (client-side)
|
|
307
368
|
--event-id <uuid> Filter by external event ID
|
|
308
369
|
--runtime-id <uuid> Filter by runtime context ID
|
|
309
370
|
--actor-id <uuid> Filter by user actor ID
|
|
@@ -325,5 +386,12 @@ Examples:
|
|
|
325
386
|
newo logs --type call --skill CreateActor # Skill calls for CreateActor
|
|
326
387
|
newo logs --flow CACreatorFlow --follow # Tail logs for specific flow
|
|
327
388
|
newo logs --json --per 100 # Get 100 logs as JSON
|
|
389
|
+
newo logs --type call --name Gen --json # Only Gen action calls
|
|
390
|
+
|
|
391
|
+
Notes:
|
|
392
|
+
The model used for a turn is in data.source.model of the --json output ā
|
|
393
|
+
do NOT infer it from actor/agent names.
|
|
394
|
+
external_event_id (newo sandbox --json) correlates a chat turn with its
|
|
395
|
+
logs: newo logs --event-id <id>
|
|
328
396
|
`);
|
|
329
397
|
}
|