@moda-ai/cli 1.21.0 → 1.22.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/README.md +3 -0
- package/dist/api-client.d.ts +1 -1
- package/dist/api-client.js +2 -2
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +109 -1
- package/dist/manifest.js +1 -0
- package/dist/output.d.ts +8 -0
- package/dist/output.js +11 -0
- package/dist/schemas.d.ts +34 -0
- package/dist/schemas.js +30 -0
- package/dist/skills.js +8 -2
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/skills/moda-cli/SKILL.md +29 -1
package/README.md
CHANGED
|
@@ -58,6 +58,9 @@ moda tool-failures # Tool failure overview
|
|
|
58
58
|
moda tool-failure-detail <tool_name> # Per-tool failure detail
|
|
59
59
|
moda tool-failure-detail <tool_name> --include-window --window=1
|
|
60
60
|
|
|
61
|
+
moda feedback "cluster label looks wrong" # Help us improve: flag wrong/missing data
|
|
62
|
+
moda feedback "..." --category=missing_data --conversation-id=<id>
|
|
63
|
+
|
|
61
64
|
# Prompt management
|
|
62
65
|
moda prompts init # Create .moda/prompts.yml
|
|
63
66
|
moda prompts status # Read-only local prompt status
|
package/dist/api-client.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export declare class ApiError extends Error {
|
|
|
14
14
|
export declare class NetworkError extends Error {
|
|
15
15
|
constructor(message: string);
|
|
16
16
|
}
|
|
17
|
-
export declare function callDataAPI(endpoint: string): Promise<unknown>;
|
|
17
|
+
export declare function callDataAPI(endpoint: string, init?: RequestInit, requestOptions?: ModaRequestOptions): Promise<unknown>;
|
|
18
18
|
/**
|
|
19
19
|
* Per-call transport overrides. Used by expensive, long-running, non-idempotent
|
|
20
20
|
* endpoints (e.g. the agent `ask` POST) that need a longer timeout than the
|
package/dist/api-client.js
CHANGED
|
@@ -31,10 +31,10 @@ export class NetworkError extends Error {
|
|
|
31
31
|
function isRetryable(status) {
|
|
32
32
|
return status === 429 || status >= 500;
|
|
33
33
|
}
|
|
34
|
-
export async function callDataAPI(endpoint) {
|
|
34
|
+
export async function callDataAPI(endpoint, init = { method: 'GET' }, requestOptions = {}) {
|
|
35
35
|
apiRequestCount++;
|
|
36
36
|
const url = `${resolveModaBaseUrl()}/api/v1/data${endpoint}`;
|
|
37
|
-
return callModaAPI(url, {
|
|
37
|
+
return callModaAPI(url, init, {}, requestOptions);
|
|
38
38
|
}
|
|
39
39
|
export async function callControlAPI(endpoint, options = {}, profileOptions = {}, requestOptions = {}) {
|
|
40
40
|
const url = `${resolveModaBaseUrl(profileOptions)}/api${endpoint}`;
|
package/dist/cli.d.ts
CHANGED
|
@@ -21,6 +21,12 @@ export declare function flagsToArgs(flags: Record<string, string>, positionalKey
|
|
|
21
21
|
* reusing this for a command with positionals.
|
|
22
22
|
*/
|
|
23
23
|
export declare function buildResumeCommand(command: string, flags: Record<string, string>): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Coarse caller identity for the feedback body's `agent` field. Same
|
|
26
|
+
* truthy-env semantics as the agent-environment detection in output.ts;
|
|
27
|
+
* never sourced from flags, because `--agent` is the output-mode flag.
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectCallerAgent(env: NodeJS.ProcessEnv): string;
|
|
24
30
|
export declare function runCommand(command: string, positional: string | undefined, flags: Record<string, string>, positionals?: string[], context?: CommandContext): Promise<number>;
|
|
25
31
|
interface CommandDispatchResult {
|
|
26
32
|
exitCode: number;
|
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, SearchSchema, WorldStateSchema, ContextSchema, AuditSchema, FrustrationsSchema, ToolFailuresSchema, ToolFailureDetailSchema, } from './schemas.js';
|
|
5
|
+
import { OverviewSchema, ClustersSchema, ClusterConversationsSchema, ConversationsSchema, SearchSchema, WorldStateSchema, ContextSchema, AuditSchema, FrustrationsSchema, ToolFailuresSchema, ToolFailureDetailSchema, FeedbackSchema, FEEDBACK_CATEGORIES, FEEDBACK_SEVERITIES, } from './schemas.js';
|
|
6
6
|
import { withFrustrationAnchors, withToolFailureAnchors, embeddedWindowSize, asRecord, asString, asNumber } from './transforms.js';
|
|
7
7
|
import { runPromptsCommand } from './prompts.js';
|
|
8
8
|
import { runSkillsCommand } from './skills.js';
|
|
@@ -170,6 +170,7 @@ Commands:
|
|
|
170
170
|
frustrations Get user frustration detections
|
|
171
171
|
tool-failures Get tool failure overview
|
|
172
172
|
tool-failure-detail <tool_name> Get per-tool failure detail
|
|
173
|
+
feedback "<note>" Flag wrong/missing data or CLI quirks to the Moda team
|
|
173
174
|
|
|
174
175
|
Prompt management:
|
|
175
176
|
prompts init Create .moda/prompts.yml
|
|
@@ -235,6 +236,16 @@ World-state flags:
|
|
|
235
236
|
--summary-only Return segment summaries only (omit the event stream)
|
|
236
237
|
--event-limit=N Max world-state events to return (1-5000, default 2000)
|
|
237
238
|
|
|
239
|
+
Feedback flags:
|
|
240
|
+
--category=CAT bad_cluster_label|mismatched_frustration|missing_data|noisy_data|
|
|
241
|
+
wrong_tool_failure|incorrect_loop|api_quirk|other (default other)
|
|
242
|
+
--severity=info|low|medium|high How bad it is (default low)
|
|
243
|
+
--conversation-id=ID Attach the conversation you were looking at
|
|
244
|
+
--cluster-id=ID Attach a cluster node id
|
|
245
|
+
--tool-name=NAME Attach a tool name
|
|
246
|
+
--run-id=ID Attach a run id
|
|
247
|
+
(set MODA_CLI_TIPS=0 to hide the feedback tip line)
|
|
248
|
+
|
|
238
249
|
Init flags:
|
|
239
250
|
--yes Accept all defaults (non-interactive)
|
|
240
251
|
--analyst=auto|claude|codex|cursor|local-scan
|
|
@@ -310,6 +321,7 @@ Examples:
|
|
|
310
321
|
moda search "refund flow" --mode=semantic --time-range=7d --limit=10
|
|
311
322
|
moda world-state <conversation_id> --summary-only
|
|
312
323
|
moda frustrations --days-back=14 --limit=20
|
|
324
|
+
moda feedback "cluster label looks wrong" --category=bad_cluster_label --cluster-id=<node_id>
|
|
313
325
|
moda context <conversation_id> --window=3
|
|
314
326
|
moda audit <conversation_id|trace_id>
|
|
315
327
|
moda audit <trace_id> --kind=trace --json
|
|
@@ -421,6 +433,21 @@ function recordConversationsTelemetry(context, data) {
|
|
|
421
433
|
has_more: hasMore === true ? 'yes' : hasMore === false ? 'no' : 'na',
|
|
422
434
|
});
|
|
423
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Coarse caller identity for the feedback body's `agent` field. Same
|
|
438
|
+
* truthy-env semantics as the agent-environment detection in output.ts;
|
|
439
|
+
* never sourced from flags, because `--agent` is the output-mode flag.
|
|
440
|
+
*/
|
|
441
|
+
export function detectCallerAgent(env) {
|
|
442
|
+
const truthy = (value) => value != null && value !== '' && value !== '0' && value.toLowerCase() !== 'false';
|
|
443
|
+
if (truthy(env.CLAUDECODE))
|
|
444
|
+
return 'claude-code';
|
|
445
|
+
if (truthy(env.CURSOR))
|
|
446
|
+
return 'cursor';
|
|
447
|
+
if (truthy(env.MODA_AGENT) || truthy(env.CI))
|
|
448
|
+
return 'agent';
|
|
449
|
+
return 'human';
|
|
450
|
+
}
|
|
424
451
|
export async function runCommand(command, positional, flags, positionals = positional ? [positional] : [], context = createCommandContext({ command, positional, positionals, flags })) {
|
|
425
452
|
switch (command) {
|
|
426
453
|
case 'prompts': {
|
|
@@ -624,6 +651,54 @@ export async function runCommand(command, positional, flags, positionals = posit
|
|
|
624
651
|
await printWithOptionalWindows(withToolFailureAnchors(data), 'examples', params.include_window === true, params.window ?? 1, context.output);
|
|
625
652
|
break;
|
|
626
653
|
}
|
|
654
|
+
case 'feedback': {
|
|
655
|
+
// Join every positional so an unquoted multi-word note is kept whole
|
|
656
|
+
// instead of silently truncating to the first word.
|
|
657
|
+
const note = positionals.join(' ').trim();
|
|
658
|
+
if (note === '') {
|
|
659
|
+
throw new CliInputError('<note> is required', `Usage: moda feedback "<note>" [--category=${FEEDBACK_CATEGORIES.join('|')}] [--severity=${FEEDBACK_SEVERITIES.join('|')}] [--conversation-id=ID] [--cluster-id=ID] [--tool-name=NAME] [--run-id=ID]`);
|
|
660
|
+
}
|
|
661
|
+
// Raw flag strings, not flagsToArgs: its numeric coercion would turn a
|
|
662
|
+
// purely numeric id ("--conversation-id=873459") into a number that
|
|
663
|
+
// FeedbackSchema's z.string() rejects.
|
|
664
|
+
const params = FeedbackSchema.parse({
|
|
665
|
+
note,
|
|
666
|
+
...(flags.category !== undefined ? { category: flags.category } : {}),
|
|
667
|
+
...(flags.severity !== undefined ? { severity: flags.severity } : {}),
|
|
668
|
+
...(flags['conversation-id'] !== undefined ? { conversation_id: flags['conversation-id'] } : {}),
|
|
669
|
+
...(flags['cluster-id'] !== undefined ? { cluster_id: flags['cluster-id'] } : {}),
|
|
670
|
+
...(flags['tool-name'] !== undefined ? { tool_name: flags['tool-name'] } : {}),
|
|
671
|
+
...(flags['run-id'] !== undefined ? { run_id: flags['run-id'] } : {}),
|
|
672
|
+
});
|
|
673
|
+
const refs = {};
|
|
674
|
+
if (params.conversation_id)
|
|
675
|
+
refs.conversation_id = params.conversation_id;
|
|
676
|
+
if (params.cluster_id)
|
|
677
|
+
refs.cluster_id = params.cluster_id;
|
|
678
|
+
if (params.tool_name)
|
|
679
|
+
refs.tool_name = params.tool_name;
|
|
680
|
+
if (params.run_id)
|
|
681
|
+
refs.run_id = params.run_id;
|
|
682
|
+
const category = params.category ?? 'other';
|
|
683
|
+
const severity = params.severity ?? 'low';
|
|
684
|
+
// retries: 0. The backend mints a fresh feedback_id per request and
|
|
685
|
+
// moda.agent_feedback has no dedup layer, so a retried POST after a
|
|
686
|
+
// lost response would insert duplicate rows.
|
|
687
|
+
const data = await callDataAPI('/feedback', {
|
|
688
|
+
method: 'POST',
|
|
689
|
+
body: JSON.stringify({
|
|
690
|
+
category,
|
|
691
|
+
severity,
|
|
692
|
+
source: 'cli',
|
|
693
|
+
agent: detectCallerAgent(process.env),
|
|
694
|
+
cli_version: VERSION,
|
|
695
|
+
note: params.note,
|
|
696
|
+
...(Object.keys(refs).length > 0 ? { refs } : {}),
|
|
697
|
+
}),
|
|
698
|
+
}, { retries: 0 });
|
|
699
|
+
context.output.writeData({ submitted: true, category, severity, ...(asRecord(data) ?? {}) }, { summary: 'Feedback submitted. Thank you for helping us improve!' });
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
627
702
|
default:
|
|
628
703
|
throw new CliInputError(`Unknown command '${command}'`, 'Run "moda --help" for usage information.');
|
|
629
704
|
}
|
|
@@ -1036,6 +1111,16 @@ export const commandRegistry = createCommandRegistry([
|
|
|
1036
1111
|
examples: ['moda tool-failure-detail search --include-window'],
|
|
1037
1112
|
...dataApiDefaults,
|
|
1038
1113
|
}),
|
|
1114
|
+
legacyCommand({
|
|
1115
|
+
name: 'feedback',
|
|
1116
|
+
description: 'Flag wrong/missing data or CLI quirks to the Moda team',
|
|
1117
|
+
examples: [
|
|
1118
|
+
'moda feedback "cluster label looks wrong" --category=bad_cluster_label --cluster-id=<node_id>',
|
|
1119
|
+
'moda feedback "search finds nothing for a conversation I can open" --category=missing_data --conversation-id=<id>',
|
|
1120
|
+
],
|
|
1121
|
+
...dataApiDefaults,
|
|
1122
|
+
mutability: 'write',
|
|
1123
|
+
}),
|
|
1039
1124
|
legacyCommand({
|
|
1040
1125
|
name: 'prompts',
|
|
1041
1126
|
description: 'Manage prompt versions and labels',
|
|
@@ -1111,6 +1196,28 @@ export function printCliError(error) {
|
|
|
1111
1196
|
safeWriteStderr(`Error: ${String(error)}\n`);
|
|
1112
1197
|
}
|
|
1113
1198
|
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Human-TTY twin of the envelope `meta.tip`: one dim stderr line after a
|
|
1201
|
+
* successful command. stderr only (stdout stays machine-readable), TTY only
|
|
1202
|
+
* (pipes/CI stay noise-free), and MODA_CLI_TIPS=0 opts out.
|
|
1203
|
+
*/
|
|
1204
|
+
function printFeedbackTip(commandName, context) {
|
|
1205
|
+
if (context.outputMode !== 'human' || context.output.quiet)
|
|
1206
|
+
return;
|
|
1207
|
+
if (commandName === 'feedback')
|
|
1208
|
+
return;
|
|
1209
|
+
if (!process.stderr.isTTY)
|
|
1210
|
+
return;
|
|
1211
|
+
// Intentional strict `=== '0'` (NOT the permissive truthy() used by
|
|
1212
|
+
// detectCallerAgent): `MODA_CLI_TIPS=0` is the one documented opt-out form
|
|
1213
|
+
// (manifest env list + docs), so keep this exact-match — don't "fix" it to
|
|
1214
|
+
// match the other guards or it silently changes the published interface.
|
|
1215
|
+
if (process.env.MODA_CLI_TIPS === '0')
|
|
1216
|
+
return;
|
|
1217
|
+
const text = 'Tip: Use `moda feedback "<note>"` to help us improve!';
|
|
1218
|
+
const colorOk = !('NO_COLOR' in process.env) && process.env.TERM !== 'dumb';
|
|
1219
|
+
safeWriteStderr(`\n${colorOk ? `\x1b[2m${text}\x1b[0m` : text}\n`);
|
|
1220
|
+
}
|
|
1114
1221
|
function printAgentCliError(command, error, mode = 'agent') {
|
|
1115
1222
|
const output = createCliOutput({
|
|
1116
1223
|
command: command || 'unknown',
|
|
@@ -1236,6 +1343,7 @@ export async function dispatchParsedCommand(parsed, startedAt = Date.now()) {
|
|
|
1236
1343
|
if (!updateCheckDisabled) {
|
|
1237
1344
|
printUpdateNotice();
|
|
1238
1345
|
}
|
|
1346
|
+
printFeedbackTip(definition.name, context);
|
|
1239
1347
|
return finishRun(context, { kind: 'completed', exitCode: result.exitCode });
|
|
1240
1348
|
}
|
|
1241
1349
|
catch (error) {
|
package/dist/manifest.js
CHANGED
package/dist/output.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ import { type AgentAuthRequest, type AgentInputRequest } from './errors.js';
|
|
|
2
2
|
export type CliOutputMode = 'human' | 'json' | 'agent' | 'agent-stream';
|
|
3
3
|
export declare const AGENT_SCHEMA_VERSION = "moda.agent.v1";
|
|
4
4
|
export declare const AGENT_EVENT_SCHEMA_VERSION = "moda.agent_event.v1";
|
|
5
|
+
/**
|
|
6
|
+
* One-line feedback nudge stamped into every non-feedback success envelope's
|
|
7
|
+
* `meta.tip` (agents are the CLI's most common callers, and the envelope is
|
|
8
|
+
* where they actually look). Deliberately conditional wording so agents flag
|
|
9
|
+
* genuine observations instead of habitually running the command. The
|
|
10
|
+
* human-TTY twin is printed on stderr by the dispatcher in cli.ts.
|
|
11
|
+
*/
|
|
12
|
+
export declare const FEEDBACK_TIP = "Help us improve! If a result in this response looks wrong or is missing data, flag it: moda feedback \"<describe what looked wrong>\" [--category=...] [--conversation-id=... | --cluster-id=... | --tool-name=...]. Only submit genuine observations.";
|
|
5
13
|
/**
|
|
6
14
|
* Stable process exit codes that an external agent can branch on:
|
|
7
15
|
* 0 ok — grounded answer from Moda Cloud intelligence.
|
package/dist/output.js
CHANGED
|
@@ -3,6 +3,14 @@ import { CliAccessError, CliAuthError, CliCancellationError, CliInputError, CliI
|
|
|
3
3
|
import { redactDeep } from './redact.js';
|
|
4
4
|
export const AGENT_SCHEMA_VERSION = 'moda.agent.v1';
|
|
5
5
|
export const AGENT_EVENT_SCHEMA_VERSION = 'moda.agent_event.v1';
|
|
6
|
+
/**
|
|
7
|
+
* One-line feedback nudge stamped into every non-feedback success envelope's
|
|
8
|
+
* `meta.tip` (agents are the CLI's most common callers, and the envelope is
|
|
9
|
+
* where they actually look). Deliberately conditional wording so agents flag
|
|
10
|
+
* genuine observations instead of habitually running the command. The
|
|
11
|
+
* human-TTY twin is printed on stderr by the dispatcher in cli.ts.
|
|
12
|
+
*/
|
|
13
|
+
export const FEEDBACK_TIP = 'Help us improve! If a result in this response looks wrong or is missing data, flag it: moda feedback "<describe what looked wrong>" [--category=...] [--conversation-id=... | --cluster-id=... | --tool-name=...]. Only submit genuine observations.';
|
|
6
14
|
/**
|
|
7
15
|
* Stable process exit codes that an external agent can branch on:
|
|
8
16
|
* 0 ok — grounded answer from Moda Cloud intelligence.
|
|
@@ -214,6 +222,9 @@ export function createCliOutput(options) {
|
|
|
214
222
|
meta: {
|
|
215
223
|
...(writeOptions.meta ?? {}),
|
|
216
224
|
run_id: state.runId,
|
|
225
|
+
// No tip on the feedback command itself; it would point at the
|
|
226
|
+
// command that just ran.
|
|
227
|
+
...(options.command === 'feedback' ? {} : { tip: FEEDBACK_TIP }),
|
|
217
228
|
},
|
|
218
229
|
summary: writeOptions.summary,
|
|
219
230
|
findings: writeOptions.findings,
|
package/dist/schemas.d.ts
CHANGED
|
@@ -188,4 +188,38 @@ export declare const SearchSchema: z.ZodObject<{
|
|
|
188
188
|
user_id?: string | undefined;
|
|
189
189
|
include_tool_io?: boolean | undefined;
|
|
190
190
|
}>;
|
|
191
|
+
export declare const FEEDBACK_CATEGORIES: readonly ["bad_cluster_label", "mismatched_frustration", "missing_data", "noisy_data", "wrong_tool_failure", "incorrect_loop", "api_quirk", "other"];
|
|
192
|
+
export declare const FEEDBACK_SEVERITIES: readonly ["info", "low", "medium", "high"];
|
|
193
|
+
/**
|
|
194
|
+
* moda feedback "<note>": flag wrong/missing data or CLI quirks to the Moda
|
|
195
|
+
* team. The handler builds this input from raw flag strings (NOT flagsToArgs,
|
|
196
|
+
* whose numeric coercion would turn "--conversation-id=873459" into a number
|
|
197
|
+
* that z.string() rejects). max(4000) on note mirrors the backend
|
|
198
|
+
* @MaxLength(4000); the capped id fields become the POST body's `refs`.
|
|
199
|
+
*/
|
|
200
|
+
export declare const FeedbackSchema: z.ZodObject<{
|
|
201
|
+
note: z.ZodString;
|
|
202
|
+
category: z.ZodOptional<z.ZodDefault<z.ZodEnum<["bad_cluster_label", "mismatched_frustration", "missing_data", "noisy_data", "wrong_tool_failure", "incorrect_loop", "api_quirk", "other"]>>>;
|
|
203
|
+
severity: z.ZodOptional<z.ZodDefault<z.ZodEnum<["info", "low", "medium", "high"]>>>;
|
|
204
|
+
conversation_id: z.ZodOptional<z.ZodString>;
|
|
205
|
+
cluster_id: z.ZodOptional<z.ZodString>;
|
|
206
|
+
tool_name: z.ZodOptional<z.ZodString>;
|
|
207
|
+
run_id: z.ZodOptional<z.ZodString>;
|
|
208
|
+
}, "strip", z.ZodTypeAny, {
|
|
209
|
+
note: string;
|
|
210
|
+
run_id?: string | undefined;
|
|
211
|
+
severity?: "high" | "medium" | "low" | "info" | undefined;
|
|
212
|
+
category?: "bad_cluster_label" | "mismatched_frustration" | "missing_data" | "noisy_data" | "wrong_tool_failure" | "incorrect_loop" | "api_quirk" | "other" | undefined;
|
|
213
|
+
conversation_id?: string | undefined;
|
|
214
|
+
tool_name?: string | undefined;
|
|
215
|
+
cluster_id?: string | undefined;
|
|
216
|
+
}, {
|
|
217
|
+
note: string;
|
|
218
|
+
run_id?: string | undefined;
|
|
219
|
+
severity?: "high" | "medium" | "low" | "info" | undefined;
|
|
220
|
+
category?: "bad_cluster_label" | "mismatched_frustration" | "missing_data" | "noisy_data" | "wrong_tool_failure" | "incorrect_loop" | "api_quirk" | "other" | undefined;
|
|
221
|
+
conversation_id?: string | undefined;
|
|
222
|
+
tool_name?: string | undefined;
|
|
223
|
+
cluster_id?: string | undefined;
|
|
224
|
+
}>;
|
|
191
225
|
//# sourceMappingURL=schemas.d.ts.map
|
package/dist/schemas.js
CHANGED
|
@@ -93,4 +93,34 @@ export const SearchSchema = z.object({
|
|
|
93
93
|
limit: z.number().min(1).max(100).default(20).optional(),
|
|
94
94
|
include_tool_io: z.boolean().default(false).optional(),
|
|
95
95
|
});
|
|
96
|
+
// Enums mirror the backend FeedbackBodyDto (VALID_FEEDBACK_CATEGORIES /
|
|
97
|
+
// VALID_FEEDBACK_SEVERITIES) so a bad value fails locally instead of
|
|
98
|
+
// round-tripping a 400.
|
|
99
|
+
export const FEEDBACK_CATEGORIES = [
|
|
100
|
+
'bad_cluster_label',
|
|
101
|
+
'mismatched_frustration',
|
|
102
|
+
'missing_data',
|
|
103
|
+
'noisy_data',
|
|
104
|
+
'wrong_tool_failure',
|
|
105
|
+
'incorrect_loop',
|
|
106
|
+
'api_quirk',
|
|
107
|
+
'other',
|
|
108
|
+
];
|
|
109
|
+
export const FEEDBACK_SEVERITIES = ['info', 'low', 'medium', 'high'];
|
|
110
|
+
/**
|
|
111
|
+
* moda feedback "<note>": flag wrong/missing data or CLI quirks to the Moda
|
|
112
|
+
* team. The handler builds this input from raw flag strings (NOT flagsToArgs,
|
|
113
|
+
* whose numeric coercion would turn "--conversation-id=873459" into a number
|
|
114
|
+
* that z.string() rejects). max(4000) on note mirrors the backend
|
|
115
|
+
* @MaxLength(4000); the capped id fields become the POST body's `refs`.
|
|
116
|
+
*/
|
|
117
|
+
export const FeedbackSchema = z.object({
|
|
118
|
+
note: z.string().min(1).max(4000),
|
|
119
|
+
category: z.enum(FEEDBACK_CATEGORIES).default('other').optional(),
|
|
120
|
+
severity: z.enum(FEEDBACK_SEVERITIES).default('low').optional(),
|
|
121
|
+
conversation_id: z.string().min(1).max(256).optional(),
|
|
122
|
+
cluster_id: z.string().min(1).max(256).optional(),
|
|
123
|
+
tool_name: z.string().min(1).max(256).optional(),
|
|
124
|
+
run_id: z.string().min(1).max(256).optional(),
|
|
125
|
+
});
|
|
96
126
|
//# sourceMappingURL=schemas.js.map
|
package/dist/skills.js
CHANGED
|
@@ -653,7 +653,7 @@ export async function runSkillsCommand(input) {
|
|
|
653
653
|
return 0;
|
|
654
654
|
}
|
|
655
655
|
if (sub !== 'pull') {
|
|
656
|
-
throw new CliInputError(sub ? `Unknown skills subcommand '${sub}'` : 'A skills subcommand is required', 'Usage: moda skills gen [--source=all|sdk
|
|
656
|
+
throw new CliInputError(sub ? `Unknown skills subcommand '${sub}'` : 'A skills subcommand is required', 'Usage: moda skills gen [--source=all|sdk] [--max-sessions=N] [--start-at=ISO] [--end-at=ISO] [--replay] [--replay-set-id=ID] [--reprocess-segments] [--force-reprocess] [--wait] | moda skills status [run-id] | moda skills proposals list [--status=ready_for_pr] | moda skills proposal apply <proposal-id> | moda skills pull [--status=approved|proposed|all] | moda skills sync [--dry-run]');
|
|
657
657
|
}
|
|
658
658
|
const status = (flags.status ?? 'approved').toLowerCase();
|
|
659
659
|
if (!VALID_STATUSES.has(status)) {
|
|
@@ -690,6 +690,11 @@ async function generateSkills(flags) {
|
|
|
690
690
|
: `${resolveIngestUrl().replace(/\/+$/, '')}/v1/skills/generate`;
|
|
691
691
|
const waitForCompletion = flags.wait === 'true' || flags['wait-for-completion'] === 'true';
|
|
692
692
|
const forceReprocess = flags['force-reprocess'] === 'true';
|
|
693
|
+
const source = normalizeSkillSource(flags.source);
|
|
694
|
+
// Soft-deprecate: still accepted so old CLIs don't hard-fail; server returns empty.
|
|
695
|
+
if (source === 'coding_agent') {
|
|
696
|
+
console.error('Note: --source=coding-agent is deprecated for skills gen (SDK-only). The run will find no coding-agent sessions; use --source=all or --source=sdk.');
|
|
697
|
+
}
|
|
693
698
|
const body = compactObject({
|
|
694
699
|
tenant_id: tenantId || undefined,
|
|
695
700
|
max_sessions: parsePositiveInt(flags['max-sessions'], 0),
|
|
@@ -699,7 +704,7 @@ async function generateSkills(flags) {
|
|
|
699
704
|
clustering_run_id: flags['clustering-run-id'] || undefined,
|
|
700
705
|
replay_set_id: flags['replay-set-id'] || flags['eval-set-id'] || undefined,
|
|
701
706
|
run_clustering: flags['no-clustering'] !== 'true',
|
|
702
|
-
source
|
|
707
|
+
source,
|
|
703
708
|
clustering_timeout_seconds: parsePositiveInt(flags['clustering-timeout-seconds'], 7200),
|
|
704
709
|
clustering_poll_seconds: parsePositiveInt(flags['clustering-poll-seconds'], 15),
|
|
705
710
|
max_cluster_pct: parseOptionalFloat(flags['max-cluster-pct']),
|
|
@@ -745,6 +750,7 @@ function compactObject(input) {
|
|
|
745
750
|
}
|
|
746
751
|
function normalizeSkillSource(value) {
|
|
747
752
|
const normalized = (value || 'all').toLowerCase().replace(/-/g, '_');
|
|
753
|
+
// coding_agent kept for soft-deprecation (empty server result + stderr note).
|
|
748
754
|
if (['all', 'sdk', 'coding_agent'].includes(normalized))
|
|
749
755
|
return normalized;
|
|
750
756
|
return 'all';
|
package/package.json
CHANGED
package/skills/moda-cli/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: moda-cli
|
|
3
|
-
version: 2.
|
|
3
|
+
version: 2.2.0
|
|
4
4
|
description: Query Moda's AI agent conversation analytics and manage code-first prompt versions from the terminal — semantic/keyword/hybrid message search, overview KPIs, topic clusters, message context, user frustration detections, tool failures, and moda prompts status/sync/promote. Use when the user asks about moda, modaflows, conversation analytics, prompt management, user frustrations, agent observability, tool failure debugging, wants to find conversations or tool calls about a topic, or wants to investigate how their AI agent is performing.
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -680,6 +680,33 @@ done
|
|
|
680
680
|
moda tool-failures | jq '.tools[] | {tool: .tool_name, failures: .failure_count}'
|
|
681
681
|
```
|
|
682
682
|
|
|
683
|
+
## Feedback: help us improve
|
|
684
|
+
|
|
685
|
+
Successful agent envelopes carry a `meta.tip` reminding you of this. When a
|
|
686
|
+
response looks wrong (a cluster label that doesn't match its conversations,
|
|
687
|
+
a frustration whose causes don't match the transcript, an empty result that
|
|
688
|
+
should not be empty, an API quirk), flag it with `moda feedback`. The Moda
|
|
689
|
+
team reads these to fix data quality issues. Only submit genuine
|
|
690
|
+
observations; never run the command with placeholder text.
|
|
691
|
+
|
|
692
|
+
```bash
|
|
693
|
+
moda feedback "cluster 'billing' is mostly refund conversations" \
|
|
694
|
+
--category=bad_cluster_label --cluster-id=<node_id>
|
|
695
|
+
moda feedback "search finds nothing for a conversation I can open" \
|
|
696
|
+
--category=missing_data --conversation-id=<id>
|
|
697
|
+
moda feedback "a cancelled call is counted as a tool failure" \
|
|
698
|
+
--category=wrong_tool_failure --tool-name=<tool>
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
- `<note>` is required (max 4000 chars). Say what looked wrong and why.
|
|
702
|
+
- `--category`: `bad_cluster_label | mismatched_frustration | missing_data |
|
|
703
|
+
noisy_data | wrong_tool_failure | incorrect_loop | api_quirk | other`
|
|
704
|
+
(default `other`).
|
|
705
|
+
- `--severity`: `info | low | medium | high` (default `low`).
|
|
706
|
+
- Attach ids when you have them: `--conversation-id`, `--cluster-id`,
|
|
707
|
+
`--tool-name`, `--run-id`; they let the team reproduce what you saw.
|
|
708
|
+
- Never include secrets or full transcripts in the note.
|
|
709
|
+
|
|
683
710
|
## Validation — how to know it worked
|
|
684
711
|
|
|
685
712
|
- Every successful command exits 0 and prints valid JSON to stdout.
|
|
@@ -748,6 +775,7 @@ npx: `npx -p @moda-ai/cli moda <command>`.
|
|
|
748
775
|
| `moda frustrations` | User frustration detections with evidence |
|
|
749
776
|
| `moda tool-failures` | Tool failure overview |
|
|
750
777
|
| `moda tool-failure-detail <tool_name>` | Per-tool failure breakdown + examples |
|
|
778
|
+
| `moda feedback "<note>"` | Flag wrong/missing data or CLI quirks to the Moda team |
|
|
751
779
|
| `moda prompts status` | Read-only local prompt status |
|
|
752
780
|
| `moda prompts diff` | Read-only local prompt diff/status |
|
|
753
781
|
| `moda prompts sync` | Upload changed prompt versions and update lockfile |
|