@moda-ai/cli 1.17.1 → 1.17.2

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.
@@ -0,0 +1,10 @@
1
+ import type { CommandContext } from './command-context.js';
2
+ import type { ProfileIOOptions } from './profiles.js';
3
+ /**
4
+ * `moda prompts propose` — turn a `prompts ab` run's failures into a revised
5
+ * candidate registered as a new (unlabeled) prompt version. Does NOT promote:
6
+ * the operator gates the candidate with a fresh `prompts ab` (ideally on the
7
+ * printed holdout cases the revision never saw) and promotes only if it wins.
8
+ */
9
+ export declare function runPromptPropose(positionals: string[], flags: Record<string, string>, profileOptions: ProfileIOOptions, context: CommandContext): Promise<void>;
10
+ //# sourceMappingURL=prompts-propose.d.ts.map
@@ -0,0 +1,76 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { isAbsolute, resolve } from 'node:path';
3
+ import { callControlAPI } from './api-client.js';
4
+ import { validateConfig } from './config.js';
5
+ // Propose is a single OpenRouter revision behind the backend; give it room.
6
+ const PROPOSE_TIMEOUT_MS = 300_000;
7
+ /**
8
+ * `moda prompts propose` — turn a `prompts ab` run's failures into a revised
9
+ * candidate registered as a new (unlabeled) prompt version. Does NOT promote:
10
+ * the operator gates the candidate with a fresh `prompts ab` (ideally on the
11
+ * printed holdout cases the revision never saw) and promotes only if it wins.
12
+ */
13
+ export async function runPromptPropose(positionals, flags, profileOptions, context) {
14
+ validateConfig();
15
+ const promptKey = flags['prompt-key'] || positionals[1];
16
+ const fromRunId = flags['from-run'] || flags['from-run-id'] || flags['run-id'];
17
+ const replaySetId = flags['set-id'] || flags['replay-set-id'];
18
+ if (!promptKey || !fromRunId || !replaySetId) {
19
+ throw new Error('Usage: moda prompts propose --prompt-key=<key> --from-run=<run_id> --set-id=<replay_set_id> [--max-dossiers=8] [--out=path.prompt.md]');
20
+ }
21
+ const maxDossiers = parsePositiveInt(flags['max-dossiers'], 8, 16);
22
+ const result = (await callControlAPI(`/prompts/${encodeURIComponent(promptKey)}/propose`, { method: 'POST', body: JSON.stringify({ fromRunId, replaySetId, maxDossiers }) }, profileOptions, { timeoutMs: PROPOSE_TIMEOUT_MS, retries: 0 }));
23
+ let outPath;
24
+ if (result.status === 'proposed' && result.content && flags.out) {
25
+ outPath = isAbsolute(flags.out) ? flags.out : resolve(process.cwd(), flags.out);
26
+ writeFileSync(outPath, result.content, 'utf8');
27
+ }
28
+ if (context.outputMode === 'human') {
29
+ printHuman(result, outPath);
30
+ }
31
+ context.output.writeData(outPath ? { ...result, outPath } : result);
32
+ }
33
+ function printHuman(result, outPath) {
34
+ const w = (s) => process.stderr.write(s + '\n');
35
+ if (result.status !== 'proposed') {
36
+ w(`No candidate produced (${result.status}): ${result.note}`);
37
+ return;
38
+ }
39
+ w(`Proposed candidate for '${result.promptKey}'`);
40
+ w(` new version: ${result.versionId} (unlabeled — not promoted)`);
41
+ w(` base version: ${result.baseVersionId ?? '(none)'}`);
42
+ w(` from run: built from ${result.dossierCount} failure dossier(s)`);
43
+ if (result.changelog)
44
+ w(` changelog: ${result.changelog}`);
45
+ if (result.risks.length)
46
+ w(` risks: ${result.risks.map((r) => `- ${r}`).join('\n ')}`);
47
+ if (outPath)
48
+ w(` written to: ${outPath}`);
49
+ w('');
50
+ w('Next — gate on holdout cases the revision never saw, then promote if it wins:');
51
+ if (result.holdoutCaseIds.length) {
52
+ w(` holdout cases (${result.holdoutCaseIds.length}): ${result.holdoutCaseIds.join(', ')}`);
53
+ }
54
+ else {
55
+ w(' (too few failures to reserve a holdout — gate on a fresh --auto-generate set)');
56
+ }
57
+ if (outPath) {
58
+ // The candidate lives only in the registry as an unlabeled version; `prompts ab`
59
+ // resolves --candidate from local file content, so point it at the written --out
60
+ // file. Using --candidate-key=<key> would resolve to the baseline and compare the
61
+ // prompt against itself.
62
+ w(` moda prompts ab --baseline-key=${result.promptKey} --candidate=${outPath} --auto-generate`);
63
+ }
64
+ else {
65
+ w(` # re-run with --out=<path.prompt.md> to materialize the candidate, then:`);
66
+ w(` moda prompts ab --baseline-key=${result.promptKey} --candidate=<path.prompt.md> --auto-generate`);
67
+ }
68
+ w(` moda prompts promote ${result.promptKey} --label=prod --version=${result.versionId}`);
69
+ }
70
+ function parsePositiveInt(raw, fallback, max) {
71
+ const n = Number.parseInt(raw ?? '', 10);
72
+ if (!Number.isFinite(n) || n <= 0)
73
+ return fallback;
74
+ return Math.min(n, max);
75
+ }
76
+ //# sourceMappingURL=prompts-propose.js.map
package/dist/prompts.js CHANGED
@@ -5,6 +5,7 @@ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
5
5
  import { callControlAPI } from './api-client.js';
6
6
  import { createCommandContext } from './command-context.js';
7
7
  import { runPromptAb } from './prompts-ab.js';
8
+ import { runPromptPropose } from './prompts-propose.js';
8
9
  const MODA_DIR = '.moda';
9
10
  const MANIFEST_PATH = join(MODA_DIR, 'prompts.yml');
10
11
  const LOCK_PATH = join(MODA_DIR, 'prompts.lock.json');
@@ -55,8 +56,11 @@ export async function runPromptsCommand(input, legacyFlags, legacyProfileOptions
55
56
  case 'ab':
56
57
  await runPromptAb(flags, profileOptions, context);
57
58
  break;
59
+ case 'propose':
60
+ await runPromptPropose(positionals, flags, profileOptions, context);
61
+ break;
58
62
  default:
59
- throw new Error(`Unknown prompts command '${subcommand}'. Use init, status, diff, sync, promote, or ab.`);
63
+ throw new Error(`Unknown prompts command '${subcommand}'. Use init, status, diff, sync, promote, ab, or propose.`);
60
64
  }
61
65
  }
62
66
  export async function initPrompts(options = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.17.1",
3
+ "version": "1.17.2",
4
4
  "description": "CLI for Moda - AI agent analytics and observability",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": "moda.skill_index.v1",
3
- "bundled_at": "2026-07-08T22:22:27.468Z",
4
- "cli_version": "1.17.1",
3
+ "bundled_at": "2026-07-09T02:37:26.434Z",
4
+ "cli_version": "1.17.2",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-node-anthropic",