@moda-ai/cli 1.7.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +121 -2
- package/dist/schemas.d.ts +47 -0
- package/dist/schemas.js +27 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import { validateConfig, VERSION, resolveApiKey } from './config.js';
|
|
4
4
|
import { callDataAPI, ApiError, getApiRequestCount, resetApiRequestCount, } from './api-client.js';
|
|
5
|
-
import { OverviewSchema, ClustersSchema, ClusterConversationsSchema, ConversationsSchema, ContextSchema, FrustrationsSchema, ToolFailuresSchema, ToolFailureDetailSchema, WorkflowsSchema, RunsSchema, RunDetailSchema, LoopsSchema, LoopDetailSchema, } from './schemas.js';
|
|
5
|
+
import { OverviewSchema, ClustersSchema, ClusterConversationsSchema, ConversationsSchema, SearchSchema, WorldStateSchema, ContextSchema, FrustrationsSchema, ToolFailuresSchema, ToolFailureDetailSchema, WorkflowsSchema, RunsSchema, RunDetailSchema, LoopsSchema, LoopDetailSchema, } from './schemas.js';
|
|
6
6
|
import { withFrustrationAnchors, withToolFailureAnchors, embeddedWindowSize, asRecord, asString, asNumber } from './transforms.js';
|
|
7
7
|
import { runPromptsCommand } from './prompts.js';
|
|
8
8
|
// Coding-agent capture (shadow workspace / Shadow Spark / trace hooks) is temporarily
|
|
@@ -137,6 +137,8 @@ Commands:
|
|
|
137
137
|
clusters Browse topic cluster hierarchy
|
|
138
138
|
cluster-conversations <node_id> List conversations in a cluster
|
|
139
139
|
conversations Search and filter conversations
|
|
140
|
+
search "<query>" Search conversation messages (keyword/semantic/hybrid)
|
|
141
|
+
world-state <conversation_id> Get a conversation's world state (slots/threads/events)
|
|
140
142
|
context <conversation_id> Get windowed conversation context
|
|
141
143
|
frustrations Get user frustration detections
|
|
142
144
|
tool-failures Get tool failure overview
|
|
@@ -183,6 +185,24 @@ Common flags:
|
|
|
183
185
|
--no-tui Show raw analyst stream instead of dashboard
|
|
184
186
|
--no-update-check Skip the daily new-version check (or MODA_CLI_UPDATE_CHECK=0)
|
|
185
187
|
|
|
188
|
+
Conversations flags:
|
|
189
|
+
--search=TEXT Substring match on the conversation summary
|
|
190
|
+
--world-state=KEYWORDS Match world-state content (slots + durable profile); comma = AND
|
|
191
|
+
--outcome=any|positive|negative Filter by conversation outcome (trajectory + frustration)
|
|
192
|
+
--include-world-state Attach each result's world-state summary
|
|
193
|
+
--user-id=ID Filter to a single user
|
|
194
|
+
--environment=ENV Filter by environment (all|development|staging|production)
|
|
195
|
+
|
|
196
|
+
Search flags:
|
|
197
|
+
--mode=keyword|semantic|hybrid Search mode (default hybrid)
|
|
198
|
+
--user-id=ID Filter to a single user
|
|
199
|
+
--time-range=RANGE Time range filter (all|1h|3d|7d|24h|30d|90d)
|
|
200
|
+
--limit=N Maximum results to return (1-100, default 20)
|
|
201
|
+
|
|
202
|
+
World-state flags:
|
|
203
|
+
--summary-only Return segment summaries only (omit the event stream)
|
|
204
|
+
--event-limit=N Max world-state events to return (1-5000, default 2000)
|
|
205
|
+
|
|
186
206
|
Runs flags:
|
|
187
207
|
--outcome=OUTCOME Filter by outcome (running|succeeded|partial|failed|loop|abandoned|unknown)
|
|
188
208
|
--class-id=ID Filter to a single workflow class (e.g. cron:helpscout-triage-hourly)
|
|
@@ -234,6 +254,11 @@ Examples:
|
|
|
234
254
|
moda overview --days-back=30
|
|
235
255
|
moda clusters --time-range=7d
|
|
236
256
|
moda conversations --search="error" --limit=5
|
|
257
|
+
moda conversations --world-state="enterprise" --outcome=positive
|
|
258
|
+
moda conversations --world-state="refund,billing" --include-world-state
|
|
259
|
+
moda search "billing error" --mode=hybrid
|
|
260
|
+
moda search "refund flow" --mode=semantic --time-range=7d --limit=10
|
|
261
|
+
moda world-state <conversation_id> --summary-only
|
|
237
262
|
moda frustrations --days-back=14 --limit=20
|
|
238
263
|
moda context <conversation_id> --window=3
|
|
239
264
|
moda workflows --time-range=7d
|
|
@@ -292,6 +317,25 @@ async function printWithOptionalWindows(enriched, listKey, includeWindow, window
|
|
|
292
317
|
const withWindows = await attachWindows(list, windowSize);
|
|
293
318
|
output.writeData({ ...enriched, [listKey]: withWindows });
|
|
294
319
|
}
|
|
320
|
+
// Hydrate each conversation row with its world-state summary (slots + segment
|
|
321
|
+
// summaries, no event stream) via the drill-in endpoint. Mirrors attachWindows:
|
|
322
|
+
// best-effort per row, never throws.
|
|
323
|
+
async function attachWorldState(rows) {
|
|
324
|
+
return Promise.all(rows.map(async (row) => {
|
|
325
|
+
const conv_id = asString(row.conversation_id) ?? '';
|
|
326
|
+
if (!conv_id) {
|
|
327
|
+
return { ...row, world_state: null };
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
const data = await callDataAPI(`/conversations/${conv_id}/world-state?summary_only=1`);
|
|
331
|
+
return { ...row, world_state: data };
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
335
|
+
return { ...row, world_state: { error: message } };
|
|
336
|
+
}
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
295
339
|
export async function runCommand(command, positional, flags, positionals = positional ? [positional] : [], context = createCommandContext({ command, positional, positionals, flags })) {
|
|
296
340
|
switch (command) {
|
|
297
341
|
case 'prompts': {
|
|
@@ -357,12 +401,65 @@ export async function runCommand(command, positional, flags, positionals = posit
|
|
|
357
401
|
query.set('time_range', params.time_range);
|
|
358
402
|
if (params.environment)
|
|
359
403
|
query.set('environment', params.environment);
|
|
404
|
+
if (params.world_state)
|
|
405
|
+
query.set('world_state', params.world_state);
|
|
406
|
+
if (params.outcome && params.outcome !== 'any')
|
|
407
|
+
query.set('outcome', params.outcome);
|
|
360
408
|
if (params.limit)
|
|
361
409
|
query.set('limit', params.limit.toString());
|
|
362
410
|
if (params.offset !== undefined)
|
|
363
411
|
query.set('offset', params.offset.toString());
|
|
364
412
|
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
365
413
|
const data = await callDataAPI(`/conversations${queryString}`);
|
|
414
|
+
if (params.include_world_state === true) {
|
|
415
|
+
const enriched = asRecord(data) ?? {};
|
|
416
|
+
const list = Array.isArray(enriched.conversations)
|
|
417
|
+
? enriched.conversations
|
|
418
|
+
: [];
|
|
419
|
+
const withState = await attachWorldState(list);
|
|
420
|
+
context.output.writeData({ ...enriched, conversations: withState });
|
|
421
|
+
}
|
|
422
|
+
else {
|
|
423
|
+
context.output.writeData(data);
|
|
424
|
+
}
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
case 'search': {
|
|
428
|
+
// Positional query mirrors world-state/context/cluster-conversations:
|
|
429
|
+
// reject an empty/whitespace query with the shared CLI input error before
|
|
430
|
+
// it reaches the schema (which also guards via q.min(1)).
|
|
431
|
+
if (!positional || positional.trim() === '') {
|
|
432
|
+
throw new CliInputError('<query> is required', 'Usage: moda search "<query>" [--mode=keyword|semantic|hybrid] [--user-id=ID] [--time-range=RANGE] [--limit=N]');
|
|
433
|
+
}
|
|
434
|
+
const args = flagsToArgs(flags, 'q', positional);
|
|
435
|
+
const params = SearchSchema.parse(args);
|
|
436
|
+
const query = new URLSearchParams();
|
|
437
|
+
query.set('q', params.q);
|
|
438
|
+
if (params.mode)
|
|
439
|
+
query.set('mode', params.mode);
|
|
440
|
+
if (params.user_id)
|
|
441
|
+
query.set('user_id', params.user_id);
|
|
442
|
+
if (params.time_range)
|
|
443
|
+
query.set('time_range', params.time_range);
|
|
444
|
+
if (params.limit)
|
|
445
|
+
query.set('limit', params.limit.toString());
|
|
446
|
+
const data = await callDataAPI(`/search?${query.toString()}`);
|
|
447
|
+
context.output.writeData(data);
|
|
448
|
+
break;
|
|
449
|
+
}
|
|
450
|
+
case 'world-state': {
|
|
451
|
+
if (!positional) {
|
|
452
|
+
throw new CliInputError('<conversation_id> is required', 'Usage: moda world-state <conversation_id> [--summary-only] [--event-limit=N]');
|
|
453
|
+
}
|
|
454
|
+
const args = flagsToArgs(flags, 'conversation_id', positional);
|
|
455
|
+
const params = WorldStateSchema.parse(args);
|
|
456
|
+
const query = new URLSearchParams();
|
|
457
|
+
if (params.summary_only === true)
|
|
458
|
+
query.set('summary_only', '1');
|
|
459
|
+
if (params.event_limit)
|
|
460
|
+
query.set('event_limit', params.event_limit.toString());
|
|
461
|
+
const queryString = query.toString() ? `?${query.toString()}` : '';
|
|
462
|
+
const data = await callDataAPI(`/conversations/${params.conversation_id}/world-state${queryString}`);
|
|
366
463
|
context.output.writeData(data);
|
|
367
464
|
break;
|
|
368
465
|
}
|
|
@@ -784,7 +881,29 @@ export const commandRegistry = createCommandRegistry([
|
|
|
784
881
|
legacyCommand({
|
|
785
882
|
name: 'conversations',
|
|
786
883
|
description: 'Search and filter conversations',
|
|
787
|
-
examples: [
|
|
884
|
+
examples: [
|
|
885
|
+
'moda conversations --search="error" --limit=5',
|
|
886
|
+
'moda conversations --world-state="enterprise" --outcome=positive',
|
|
887
|
+
'moda conversations --world-state="refund,enterprise" --include-world-state',
|
|
888
|
+
],
|
|
889
|
+
...dataApiDefaults,
|
|
890
|
+
}),
|
|
891
|
+
legacyCommand({
|
|
892
|
+
name: 'search',
|
|
893
|
+
description: 'Search conversation messages (keyword, semantic, or hybrid)',
|
|
894
|
+
examples: [
|
|
895
|
+
'moda search "billing error"',
|
|
896
|
+
'moda search "refund flow" --mode=semantic --time-range=7d --limit=10',
|
|
897
|
+
],
|
|
898
|
+
...dataApiDefaults,
|
|
899
|
+
}),
|
|
900
|
+
legacyCommand({
|
|
901
|
+
name: 'world-state',
|
|
902
|
+
description: "Get a conversation's world state (slots, threads, events)",
|
|
903
|
+
examples: [
|
|
904
|
+
'moda world-state <conversation_id>',
|
|
905
|
+
'moda world-state <conversation_id> --summary-only',
|
|
906
|
+
],
|
|
788
907
|
...dataApiDefaults,
|
|
789
908
|
}),
|
|
790
909
|
legacyCommand({
|
package/dist/schemas.d.ts
CHANGED
|
@@ -35,6 +35,9 @@ export declare const ConversationsSchema: z.ZodObject<{
|
|
|
35
35
|
user_id: z.ZodOptional<z.ZodString>;
|
|
36
36
|
time_range: z.ZodOptional<z.ZodDefault<z.ZodEnum<["all", "1h", "3d", "24h", "7d", "30d", "90d"]>>>;
|
|
37
37
|
environment: z.ZodOptional<z.ZodDefault<z.ZodEnum<["all", "development", "staging", "production"]>>>;
|
|
38
|
+
world_state: z.ZodOptional<z.ZodString>;
|
|
39
|
+
outcome: z.ZodOptional<z.ZodDefault<z.ZodEnum<["any", "positive", "negative"]>>>;
|
|
40
|
+
include_world_state: z.ZodOptional<z.ZodEffects<z.ZodBoolean, boolean, unknown>>;
|
|
38
41
|
limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
39
42
|
offset: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
40
43
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -45,6 +48,9 @@ export declare const ConversationsSchema: z.ZodObject<{
|
|
|
45
48
|
offset?: number | undefined;
|
|
46
49
|
cluster_id?: string | undefined;
|
|
47
50
|
user_id?: string | undefined;
|
|
51
|
+
world_state?: string | undefined;
|
|
52
|
+
outcome?: "any" | "positive" | "negative" | undefined;
|
|
53
|
+
include_world_state?: boolean | undefined;
|
|
48
54
|
}, {
|
|
49
55
|
search?: string | undefined;
|
|
50
56
|
environment?: "staging" | "all" | "development" | "production" | undefined;
|
|
@@ -53,6 +59,23 @@ export declare const ConversationsSchema: z.ZodObject<{
|
|
|
53
59
|
offset?: number | undefined;
|
|
54
60
|
cluster_id?: string | undefined;
|
|
55
61
|
user_id?: string | undefined;
|
|
62
|
+
world_state?: string | undefined;
|
|
63
|
+
outcome?: "any" | "positive" | "negative" | undefined;
|
|
64
|
+
include_world_state?: unknown;
|
|
65
|
+
}>;
|
|
66
|
+
/** moda world-state <conversationId> — per-conversation world-state drill-in. */
|
|
67
|
+
export declare const WorldStateSchema: z.ZodObject<{
|
|
68
|
+
conversation_id: z.ZodString;
|
|
69
|
+
summary_only: z.ZodOptional<z.ZodEffects<z.ZodBoolean, boolean, unknown>>;
|
|
70
|
+
event_limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
71
|
+
}, "strip", z.ZodTypeAny, {
|
|
72
|
+
conversation_id: string;
|
|
73
|
+
summary_only?: boolean | undefined;
|
|
74
|
+
event_limit?: number | undefined;
|
|
75
|
+
}, {
|
|
76
|
+
conversation_id: string;
|
|
77
|
+
summary_only?: unknown;
|
|
78
|
+
event_limit?: number | undefined;
|
|
56
79
|
}>;
|
|
57
80
|
export declare const ContextSchema: z.ZodObject<{
|
|
58
81
|
conversation_id: z.ZodString;
|
|
@@ -118,6 +141,30 @@ export declare const ToolFailureDetailSchema: z.ZodObject<{
|
|
|
118
141
|
include_window?: unknown;
|
|
119
142
|
subtype?: string | undefined;
|
|
120
143
|
}>;
|
|
144
|
+
/**
|
|
145
|
+
* moda search "<query>" — keyword/semantic/hybrid search across conversation
|
|
146
|
+
* messages. `q` arrives as a positional arg; the runCommand handler rejects an
|
|
147
|
+
* empty/whitespace query before parsing, and min(1) guards the schema path too.
|
|
148
|
+
*/
|
|
149
|
+
export declare const SearchSchema: z.ZodObject<{
|
|
150
|
+
q: z.ZodString;
|
|
151
|
+
mode: z.ZodOptional<z.ZodDefault<z.ZodEnum<["keyword", "semantic", "hybrid"]>>>;
|
|
152
|
+
user_id: z.ZodOptional<z.ZodString>;
|
|
153
|
+
time_range: z.ZodOptional<z.ZodDefault<z.ZodEnum<["all", "1h", "3d", "7d", "24h", "30d", "90d"]>>>;
|
|
154
|
+
limit: z.ZodOptional<z.ZodDefault<z.ZodNumber>>;
|
|
155
|
+
}, "strip", z.ZodTypeAny, {
|
|
156
|
+
q: string;
|
|
157
|
+
mode?: "keyword" | "semantic" | "hybrid" | undefined;
|
|
158
|
+
time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
|
|
159
|
+
limit?: number | undefined;
|
|
160
|
+
user_id?: string | undefined;
|
|
161
|
+
}, {
|
|
162
|
+
q: string;
|
|
163
|
+
mode?: "keyword" | "semantic" | "hybrid" | undefined;
|
|
164
|
+
time_range?: "all" | "1h" | "3d" | "7d" | "24h" | "30d" | "90d" | undefined;
|
|
165
|
+
limit?: number | undefined;
|
|
166
|
+
user_id?: string | undefined;
|
|
167
|
+
}>;
|
|
121
168
|
/** moda workflows — recurring workflow catalog (read from agent_job_classes). */
|
|
122
169
|
export declare const WorkflowsSchema: z.ZodObject<{
|
|
123
170
|
time_range: z.ZodOptional<z.ZodDefault<z.ZodEnum<["all", "1h", "3d", "7d", "24h", "30d", "90d"]>>>;
|
package/dist/schemas.js
CHANGED
|
@@ -25,9 +25,23 @@ export const ConversationsSchema = z.object({
|
|
|
25
25
|
user_id: z.string().optional(),
|
|
26
26
|
time_range: z.enum(['all', '1h', '3d', '24h', '7d', '30d', '90d']).default('all').optional(),
|
|
27
27
|
environment: z.enum(['all', 'development', 'staging', 'production']).default('all').optional(),
|
|
28
|
+
// Keyword(s) matched against conversation world-state content (segment slots
|
|
29
|
+
// + carried-in durable profile slots). Comma-separated terms are ANDed.
|
|
30
|
+
// max(200) mirrors the backend ConversationsQueryDto @MaxLength(200) so an
|
|
31
|
+
// over-long value fails locally instead of round-tripping a 400.
|
|
32
|
+
world_state: z.string().max(200).optional(),
|
|
33
|
+
// Filter by conversation outcome (trajectory + frustration).
|
|
34
|
+
outcome: z.enum(['any', 'positive', 'negative']).default('any').optional(),
|
|
35
|
+
include_world_state: boolFlag(),
|
|
28
36
|
limit: z.number().min(1).max(100).default(20).optional(),
|
|
29
37
|
offset: z.number().min(0).default(0).optional(),
|
|
30
38
|
});
|
|
39
|
+
/** moda world-state <conversationId> — per-conversation world-state drill-in. */
|
|
40
|
+
export const WorldStateSchema = z.object({
|
|
41
|
+
conversation_id: z.string(),
|
|
42
|
+
summary_only: boolFlag(),
|
|
43
|
+
event_limit: z.number().min(1).max(5000).default(2000).optional(),
|
|
44
|
+
});
|
|
31
45
|
export const ContextSchema = z.object({
|
|
32
46
|
conversation_id: z.string(),
|
|
33
47
|
msg_index: z.number().min(0).optional(),
|
|
@@ -62,6 +76,19 @@ export const ToolFailureDetailSchema = z.object({
|
|
|
62
76
|
// `moda workflows` lists the catalog; `moda runs` lists executions.
|
|
63
77
|
const RUN_OUTCOMES = ['running', 'succeeded', 'partial', 'failed', 'loop', 'abandoned', 'unknown'];
|
|
64
78
|
const TIME_RANGES = ['all', '1h', '3d', '7d', '24h', '30d', '90d'];
|
|
79
|
+
const SEARCH_MODES = ['keyword', 'semantic', 'hybrid'];
|
|
80
|
+
/**
|
|
81
|
+
* moda search "<query>" — keyword/semantic/hybrid search across conversation
|
|
82
|
+
* messages. `q` arrives as a positional arg; the runCommand handler rejects an
|
|
83
|
+
* empty/whitespace query before parsing, and min(1) guards the schema path too.
|
|
84
|
+
*/
|
|
85
|
+
export const SearchSchema = z.object({
|
|
86
|
+
q: z.string().min(1).max(500),
|
|
87
|
+
mode: z.enum(SEARCH_MODES).default('hybrid').optional(),
|
|
88
|
+
user_id: z.string().optional(),
|
|
89
|
+
time_range: z.enum(TIME_RANGES).default('all').optional(),
|
|
90
|
+
limit: z.number().min(1).max(100).default(20).optional(),
|
|
91
|
+
});
|
|
65
92
|
/** moda workflows — recurring workflow catalog (read from agent_job_classes). */
|
|
66
93
|
export const WorkflowsSchema = z.object({
|
|
67
94
|
time_range: z.enum(TIME_RANGES).default('all').optional(),
|