@moda-ai/cli 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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, 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,7 @@ 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
+ world-state <conversation_id> Get a conversation's world state (slots/threads/events)
140
141
  context <conversation_id> Get windowed conversation context
141
142
  frustrations Get user frustration detections
142
143
  tool-failures Get tool failure overview
@@ -183,6 +184,18 @@ Common flags:
183
184
  --no-tui Show raw analyst stream instead of dashboard
184
185
  --no-update-check Skip the daily new-version check (or MODA_CLI_UPDATE_CHECK=0)
185
186
 
187
+ Conversations flags:
188
+ --search=TEXT Substring match on the conversation summary
189
+ --world-state=KEYWORDS Match world-state content (slots + durable profile); comma = AND
190
+ --outcome=any|positive|negative Filter by conversation outcome (trajectory + frustration)
191
+ --include-world-state Attach each result's world-state summary
192
+ --user-id=ID Filter to a single user
193
+ --environment=ENV Filter by environment (all|development|staging|production)
194
+
195
+ World-state flags:
196
+ --summary-only Return segment summaries only (omit the event stream)
197
+ --event-limit=N Max world-state events to return (1-5000, default 2000)
198
+
186
199
  Runs flags:
187
200
  --outcome=OUTCOME Filter by outcome (running|succeeded|partial|failed|loop|abandoned|unknown)
188
201
  --class-id=ID Filter to a single workflow class (e.g. cron:helpscout-triage-hourly)
@@ -234,6 +247,9 @@ Examples:
234
247
  moda overview --days-back=30
235
248
  moda clusters --time-range=7d
236
249
  moda conversations --search="error" --limit=5
250
+ moda conversations --world-state="enterprise" --outcome=positive
251
+ moda conversations --world-state="refund,billing" --include-world-state
252
+ moda world-state <conversation_id> --summary-only
237
253
  moda frustrations --days-back=14 --limit=20
238
254
  moda context <conversation_id> --window=3
239
255
  moda workflows --time-range=7d
@@ -292,6 +308,25 @@ async function printWithOptionalWindows(enriched, listKey, includeWindow, window
292
308
  const withWindows = await attachWindows(list, windowSize);
293
309
  output.writeData({ ...enriched, [listKey]: withWindows });
294
310
  }
311
+ // Hydrate each conversation row with its world-state summary (slots + segment
312
+ // summaries, no event stream) via the drill-in endpoint. Mirrors attachWindows:
313
+ // best-effort per row, never throws.
314
+ async function attachWorldState(rows) {
315
+ return Promise.all(rows.map(async (row) => {
316
+ const conv_id = asString(row.conversation_id) ?? '';
317
+ if (!conv_id) {
318
+ return { ...row, world_state: null };
319
+ }
320
+ try {
321
+ const data = await callDataAPI(`/conversations/${conv_id}/world-state?summary_only=1`);
322
+ return { ...row, world_state: data };
323
+ }
324
+ catch (err) {
325
+ const message = err instanceof Error ? err.message : String(err);
326
+ return { ...row, world_state: { error: message } };
327
+ }
328
+ }));
329
+ }
295
330
  export async function runCommand(command, positional, flags, positionals = positional ? [positional] : [], context = createCommandContext({ command, positional, positionals, flags })) {
296
331
  switch (command) {
297
332
  case 'prompts': {
@@ -357,12 +392,42 @@ export async function runCommand(command, positional, flags, positionals = posit
357
392
  query.set('time_range', params.time_range);
358
393
  if (params.environment)
359
394
  query.set('environment', params.environment);
395
+ if (params.world_state)
396
+ query.set('world_state', params.world_state);
397
+ if (params.outcome && params.outcome !== 'any')
398
+ query.set('outcome', params.outcome);
360
399
  if (params.limit)
361
400
  query.set('limit', params.limit.toString());
362
401
  if (params.offset !== undefined)
363
402
  query.set('offset', params.offset.toString());
364
403
  const queryString = query.toString() ? `?${query.toString()}` : '';
365
404
  const data = await callDataAPI(`/conversations${queryString}`);
405
+ if (params.include_world_state === true) {
406
+ const enriched = asRecord(data) ?? {};
407
+ const list = Array.isArray(enriched.conversations)
408
+ ? enriched.conversations
409
+ : [];
410
+ const withState = await attachWorldState(list);
411
+ context.output.writeData({ ...enriched, conversations: withState });
412
+ }
413
+ else {
414
+ context.output.writeData(data);
415
+ }
416
+ break;
417
+ }
418
+ case 'world-state': {
419
+ if (!positional) {
420
+ throw new CliInputError('<conversation_id> is required', 'Usage: moda world-state <conversation_id> [--summary-only] [--event-limit=N]');
421
+ }
422
+ const args = flagsToArgs(flags, 'conversation_id', positional);
423
+ const params = WorldStateSchema.parse(args);
424
+ const query = new URLSearchParams();
425
+ if (params.summary_only === true)
426
+ query.set('summary_only', '1');
427
+ if (params.event_limit)
428
+ query.set('event_limit', params.event_limit.toString());
429
+ const queryString = query.toString() ? `?${query.toString()}` : '';
430
+ const data = await callDataAPI(`/conversations/${params.conversation_id}/world-state${queryString}`);
366
431
  context.output.writeData(data);
367
432
  break;
368
433
  }
@@ -784,7 +849,20 @@ export const commandRegistry = createCommandRegistry([
784
849
  legacyCommand({
785
850
  name: 'conversations',
786
851
  description: 'Search and filter conversations',
787
- examples: ['moda conversations --search="error" --limit=5'],
852
+ examples: [
853
+ 'moda conversations --search="error" --limit=5',
854
+ 'moda conversations --world-state="enterprise" --outcome=positive',
855
+ 'moda conversations --world-state="refund,enterprise" --include-world-state',
856
+ ],
857
+ ...dataApiDefaults,
858
+ }),
859
+ legacyCommand({
860
+ name: 'world-state',
861
+ description: "Get a conversation's world state (slots, threads, events)",
862
+ examples: [
863
+ 'moda world-state <conversation_id>',
864
+ 'moda world-state <conversation_id> --summary-only',
865
+ ],
788
866
  ...dataApiDefaults,
789
867
  }),
790
868
  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;
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(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "CLI for Moda - AI agent analytics and observability",
5
5
  "type": "module",
6
6
  "bin": {