@moda-ai/cli 1.55.42 → 1.55.44

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.
Files changed (45) hide show
  1. package/README.md +1 -1
  2. package/dist/{agent-86hghww1.js → agent-5ax7fqhe.js} +9 -8
  3. package/dist/{cli-daqkdnm1.js → cli-4pc0a18x.js} +1 -1
  4. package/dist/{cli-1z1sx8dg.js → cli-5aa48ry5.js} +5 -4
  5. package/dist/{cli-xddtv3yn.js → cli-8pcq2et6.js} +2 -40
  6. package/dist/{cli-hjm9151g.js → cli-8pksf2x7.js} +5 -5
  7. package/dist/{cli-vwpbspr5.js → cli-d4cdczhe.js} +5 -5
  8. package/dist/{cli-0xc5htv7.js → cli-dmm9jm9m.js} +1 -1
  9. package/dist/cli-e0kbye4s.js +39 -0
  10. package/dist/{cli-t0pb7qbs.js → cli-e24fjyrb.js} +118 -10
  11. package/dist/{cli-gt3yq1y5.js → cli-k2ka2kjb.js} +184 -2
  12. package/dist/{cli-pmzh2qjy.js → cli-sejqvvhv.js} +14 -6
  13. package/dist/{cli-srwzar82.js → cli-t4xfvrry.js} +34 -15
  14. package/dist/{cli-4sy4tytm.js → cli-tztzrh6b.js} +19 -5
  15. package/dist/{cli-he8csgsr.js → cli-wwbr5caw.js} +8 -8
  16. package/dist/{cli-77ekrx7d.js → cli-xcpwp0gb.js} +14 -7
  17. package/dist/{cli-mxfyq5nq.js → cli-xx0pjzkh.js} +4 -4
  18. package/dist/cli.js +82 -41
  19. package/dist/{doctor-c05njdv8.js → doctor-k4tdgvgv.js} +8 -7
  20. package/dist/{fix-aghp304m.js → fix-nk5xffz8.js} +4 -4
  21. package/dist/{harness-vabp33ak.js → harness-avynbq4f.js} +6 -5
  22. package/dist/{harness-github-actions-112503xs.js → harness-github-actions-gd6vyxb0.js} +6 -5
  23. package/dist/{index-dtxvvcq2.js → index-63k2a6gb.js} +12 -11
  24. package/dist/{production-intelligence-e5vsc6cw.js → production-intelligence-sv3qx7f4.js} +24 -7
  25. package/dist/{profile-commands-se9947c1.js → profile-commands-mh3kfqkn.js} +98 -20
  26. package/dist/{prompt-attribution-pbns1ya3.js → prompt-attribution-wvvnzw4m.js} +6 -5
  27. package/dist/prompt-source-mcp.js +6 -5
  28. package/dist/{prompts-ab-f5qrvpkt.js → prompts-ab-y7egdzg4.js} +8 -7
  29. package/dist/{prompts-propose-jg2j41wx.js → prompts-propose-5nw25v9a.js} +8 -7
  30. package/dist/{prompts-qppk0mhp.js → prompts-snnbyxmz.js} +6 -5
  31. package/dist/{provision-cya5rpxa.js → provision-t1dpzcjp.js} +6 -5
  32. package/dist/{registry-8a1940sr.js → registry-8zkxefjs.js} +3 -3
  33. package/dist/{skills-sh93b156.js → skills-d4215fa9.js} +6 -5
  34. package/dist/{sync-0pp1c8hs.js → sync-17f9yxb1.js} +8 -7
  35. package/package.json +1 -1
  36. package/skills/integration/index.json +2 -2
  37. package/skills/moda-cli/SKILL.md +6 -3
  38. package/skills/moda-cli/references/auth.md +16 -7
  39. package/skills/moda-cli/references/commands.md +4 -2
  40. package/skills/moda-cli/references/dashboard-parity.md +3 -0
  41. package/skills/moda-cli/references/detections.md +20 -0
  42. package/skills/moda-cli/references/output-protocol.md +1 -1
  43. package/skills/moda-cli/references/production-intelligence.md +48 -0
  44. package/skills/moda-cli/references/search-and-traces.md +3 -1
  45. package/skills/moda-cli/references/troubleshooting.md +14 -0
@@ -2,7 +2,7 @@ import {
2
2
  probeApiKey,
3
3
  redactApiKey,
4
4
  resolveApiKeyCredential
5
- } from "./cli-77ekrx7d.js";
5
+ } from "./cli-xcpwp0gb.js";
6
6
  import {
7
7
  authFetch,
8
8
  clearAuthSession,
@@ -10,20 +10,29 @@ import {
10
10
  loadAuthSession,
11
11
  login,
12
12
  revokeCliSessionToken
13
- } from "./cli-pmzh2qjy.js";
13
+ } from "./cli-sejqvvhv.js";
14
+ import {
15
+ ApiError
16
+ } from "./cli-dmm9jm9m.js";
14
17
  import {
15
18
  DEFAULT_INGEST_URL,
16
19
  EXIT_AUTH_REQUIRED,
17
20
  EXIT_DEGRADED,
18
21
  EXIT_OK,
19
22
  PROD_BASE_URL,
23
+ apiForbiddenError,
24
+ apiForbiddenReason,
20
25
  clearStoredApiKeyCredentials,
21
26
  describeBaseUrlSource,
27
+ describeModaBaseUrl,
22
28
  embeddedTenantIdFromApiKey,
23
29
  ensureSecretFilePermissions,
30
+ errorBodyFields,
24
31
  getActiveProfile,
25
32
  getModaPaths,
26
33
  loadProfileConfig,
34
+ recordApiRequest,
35
+ refusedBaseUrlMessage,
27
36
  resolveApiKey,
28
37
  resolveApiKeyFromActiveProfile,
29
38
  resolveApiTenantId,
@@ -35,7 +44,7 @@ import {
35
44
  setActiveProfile,
36
45
  shellArg,
37
46
  upsertProfile
38
- } from "./cli-t0pb7qbs.js";
47
+ } from "./cli-e24fjyrb.js";
39
48
  import {
40
49
  CliInputError
41
50
  } from "./cli-k4tqwah4.js";
@@ -44,6 +53,30 @@ import"./cli-0v6na3yp.js";
44
53
 
45
54
  // src/profile-commands.ts
46
55
  var ONLINE_PROBE_TIMEOUT_MS = 5000;
56
+ function forbiddenProbeError(probe) {
57
+ if (probe.status !== 403)
58
+ return;
59
+ const fields = errorBodyFields(probe.body);
60
+ const reason = apiForbiddenReason(fields.code, fields.message);
61
+ if (!reason || reason === "read_only_key")
62
+ return;
63
+ return new ApiError(`API error (403): ${fields.message ?? "forbidden"}`, 403, probe.body ?? "");
64
+ }
65
+ async function probeDataApiReachable(baseUrl) {
66
+ try {
67
+ recordApiRequest();
68
+ const response = await fetch(`${baseUrl}/api/v1/data/whoami`, {
69
+ method: "GET",
70
+ signal: AbortSignal.timeout(ONLINE_PROBE_TIMEOUT_MS)
71
+ });
72
+ await response.body?.cancel().catch(() => {
73
+ return;
74
+ });
75
+ return { reachable: true, status: response.status, ok: false, error: "no API key to verify" };
76
+ } catch (error) {
77
+ return { reachable: false, ok: false, error: error instanceof Error ? error.message : String(error) };
78
+ }
79
+ }
47
80
  function subcommand(context) {
48
81
  return context.positionals[0];
49
82
  }
@@ -131,6 +164,9 @@ async function loginWithApiKey(context, candidate) {
131
164
  const authOptions = { profile: context.profile, env: context.env };
132
165
  const probe = await probeApiKey(key, authOptions);
133
166
  if (probe.outcome === "rejected") {
167
+ const forbidden = forbiddenProbeError(probe);
168
+ if (forbidden)
169
+ throw forbidden;
134
170
  throw new CliInputError(`API key rejected by the Moda API (HTTP ${probe.status}).`, 'The key is revoked, malformed, or belongs to another environment. Mint a new one with "moda provision --tenant-id=<org-id>", or check MODA_BASE_URL points at the right environment.');
135
171
  }
136
172
  if (probe.outcome === "unreachable") {
@@ -205,6 +241,9 @@ async function runAuthCommand(context) {
205
241
  env: context.env
206
242
  });
207
243
  if (probe.outcome === "rejected") {
244
+ const forbidden = forbiddenProbeError(probe);
245
+ if (forbidden)
246
+ throw forbidden;
208
247
  throw new CliInputError(`API key rejected by the Moda API (HTTP ${probe.status}).`, 'The key is revoked or belongs to another environment. Mint a new one with "moda provision --tenant-id=<org-id>", or run "moda auth login".');
209
248
  }
210
249
  context.output.writeData({
@@ -260,28 +299,67 @@ async function runAuthCommand(context) {
260
299
  if (session?.user_email) {
261
300
  data.user = { email: session.user_email };
262
301
  }
302
+ const warnings = [];
263
303
  if (boolFlag(context, "online")) {
264
- let online = { reachable: false };
265
- try {
266
- const response = await authFetch("/api/cli/context", { signal: AbortSignal.timeout(ONLINE_PROBE_TIMEOUT_MS) }, authOptions);
267
- online = { reachable: true, ok: response.ok };
268
- if (response.ok) {
269
- const body = await response.json();
270
- if (Array.isArray(body.tenants)) {
271
- online.tenantCount = body.tenants.length;
304
+ const baseUrl = resolveModaBaseUrl(authOptions);
305
+ let sessionProbe;
306
+ let tenantCount;
307
+ if (session) {
308
+ sessionProbe = { reachable: false, ok: false };
309
+ try {
310
+ const response = await authFetch("/api/cli/context", { signal: AbortSignal.timeout(ONLINE_PROBE_TIMEOUT_MS) }, authOptions);
311
+ sessionProbe = { reachable: true, status: response.status, ok: response.ok };
312
+ if (response.ok) {
313
+ const body = await response.json();
314
+ if (Array.isArray(body.tenants))
315
+ tenantCount = body.tenants.length;
272
316
  }
273
- }
274
- } catch {}
275
- data.online = online;
317
+ } catch {}
318
+ }
319
+ let dataApi;
276
320
  if (credential && data.apiKeyCredential.present) {
277
321
  const probe = await probeApiKey(credential.key, authOptions);
278
322
  data.apiKeyCredential.verified = probe.outcome === "live";
279
- if (probe.outcome === "rejected") {
280
- data.authenticated = loggedIn;
323
+ if (probe.outcome === "live") {
324
+ const identity = probe.identity;
325
+ dataApi = {
326
+ reachable: true,
327
+ status: 200,
328
+ ok: true,
329
+ ...typeof identity?.tenant_id === "string" ? { tenantId: identity.tenant_id } : {},
330
+ ...identity && "tenant_name" in identity ? { tenantName: typeof identity.tenant_name === "string" ? identity.tenant_name : null } : {},
331
+ ...typeof identity?.key_kind === "string" ? { keyKind: identity.key_kind } : {},
332
+ ...typeof identity?.scope === "string" ? { scope: identity.scope } : {}
333
+ };
334
+ } else if (probe.outcome === "rejected") {
335
+ const fields = errorBodyFields(probe.body);
336
+ dataApi = { reachable: true, status: probe.status, ok: false, error: fields.message ?? `HTTP ${probe.status}` };
337
+ const reason = apiForbiddenReason(fields.code, fields.message);
338
+ if (reason && reason !== "read_only_key") {
339
+ const described = apiForbiddenError(reason, fields.message);
340
+ warnings.push(`${described.message} ${described.cause}`);
341
+ } else {
342
+ data.authenticated = loggedIn;
343
+ }
344
+ } else {
345
+ dataApi = { reachable: probe.status !== undefined, ...probe.status !== undefined ? { status: probe.status } : {}, ok: false, error: probe.reason };
281
346
  }
347
+ } else {
348
+ dataApi = await probeDataApiReachable(baseUrl);
349
+ }
350
+ data.online = {
351
+ baseUrl,
352
+ reachable: dataApi.reachable || sessionProbe?.reachable === true,
353
+ ok: (credential ? dataApi.ok : true) && (sessionProbe ? sessionProbe.ok : true) && Boolean(credential || sessionProbe),
354
+ ...tenantCount !== undefined ? { tenantCount } : {},
355
+ ...sessionProbe ? { session: sessionProbe } : {},
356
+ dataApi
357
+ };
358
+ if (sessionProbe?.status === 404 && dataApi.reachable) {
359
+ warnings.push(`${baseUrl} answers the Data API but has no /api/cli/context, so it looks like the backend. ` + "Data commands work against it; browser login and control-plane commands need the frontend URL (http://localhost:3000 locally).");
282
360
  }
283
361
  }
284
- context.output.writeData(data);
362
+ context.output.writeData(data, warnings.length > 0 ? { warnings } : undefined);
285
363
  return { exitCode: data.authenticated ? EXIT_OK : EXIT_AUTH_REQUIRED };
286
364
  }
287
365
  case "token": {
@@ -417,7 +495,7 @@ async function runConfigCommand(context) {
417
495
  const options = { profile: profile.name, env: context.env };
418
496
  const envProfile = context.env.MODA_PROFILE?.trim();
419
497
  const apiKey = resolveApiKey(options);
420
- const baseUrl = resolveModaBaseUrl(options);
498
+ const base = describeModaBaseUrl(options);
421
499
  context.output.writeData({
422
500
  moda_dir: getModaPaths({ env: context.env }).dir,
423
501
  active_profile: profile.name,
@@ -427,14 +505,14 @@ async function runConfigCommand(context) {
427
505
  resolved: {
428
506
  api_key: redactSecret(apiKey),
429
507
  api_key_source: context.env.MODA_API_KEY ? "env:MODA_API_KEY" : profile.api_key_ref === "legacy:config.api_key" && apiKey ? "legacy_config" : resolveApiKeyFromActiveProfile(options) ? "profile" : apiKey ? "legacy_config" : "none",
430
- base_url: baseUrl,
508
+ base_url: base.refused ? null : base.url,
431
509
  base_url_source: describeBaseUrlSource(options),
432
510
  ingest_url: resolveIngestUrl(options),
433
511
  tenant_id: resolveApiTenantId(options),
434
512
  workspace_id: profile.workspace_id
435
513
  },
436
514
  migrations: config.migrations
437
- });
515
+ }, base.refused ? { warnings: [`${refusedBaseUrlMessage(base.refused)} Data commands exit 5 until you re-run with --profile=local or unset MODA_BASE_URL.`] } : undefined);
438
516
  return { exitCode: 0 };
439
517
  }
440
518
  export {
@@ -1,17 +1,18 @@
1
1
  import {
2
2
  inventoryFiles,
3
3
  safePromptEvidencePath
4
- } from "./cli-vwpbspr5.js";
4
+ } from "./cli-d4cdczhe.js";
5
5
  import"./cli-xq45etg1.js";
6
6
  import"./cli-97fnztpy.js";
7
7
  import"./cli-cesds766.js";
8
8
  import {
9
9
  require_typescript
10
10
  } from "./cli-qpq84eee.js";
11
- import"./cli-xddtv3yn.js";
12
- import"./cli-4sy4tytm.js";
13
- import"./cli-0xc5htv7.js";
14
- import"./cli-t0pb7qbs.js";
11
+ import"./cli-8pcq2et6.js";
12
+ import"./cli-e0kbye4s.js";
13
+ import"./cli-tztzrh6b.js";
14
+ import"./cli-dmm9jm9m.js";
15
+ import"./cli-e24fjyrb.js";
15
16
  import"./cli-k4tqwah4.js";
16
17
  import"./cli-9kfxkxmk.js";
17
18
  import"./cli-ns5fcnkz.js";
@@ -3,7 +3,7 @@ import {
3
3
  checkPromptMapping,
4
4
  discoverHarnessPromptSources,
5
5
  safePromptEvidencePath
6
- } from "./cli-vwpbspr5.js";
6
+ } from "./cli-d4cdczhe.js";
7
7
  import"./cli-xq45etg1.js";
8
8
  import"./cli-97fnztpy.js";
9
9
  import"./cli-cesds766.js";
@@ -11,10 +11,11 @@ import {
11
11
  inspectCodeSelectors,
12
12
  inspectPythonSelectors
13
13
  } from "./cli-qpq84eee.js";
14
- import"./cli-xddtv3yn.js";
15
- import"./cli-4sy4tytm.js";
16
- import"./cli-0xc5htv7.js";
17
- import"./cli-t0pb7qbs.js";
14
+ import"./cli-8pcq2et6.js";
15
+ import"./cli-e0kbye4s.js";
16
+ import"./cli-tztzrh6b.js";
17
+ import"./cli-dmm9jm9m.js";
18
+ import"./cli-e24fjyrb.js";
18
19
  import"./cli-k4tqwah4.js";
19
20
  import"./cli-9kfxkxmk.js";
20
21
  import"./cli-ns5fcnkz.js";
@@ -23,14 +23,15 @@ import {
23
23
  runPromptAb,
24
24
  selectProblemCases,
25
25
  waitLabel
26
- } from "./cli-hjm9151g.js";
27
- import"./cli-he8csgsr.js";
28
- import"./cli-daqkdnm1.js";
26
+ } from "./cli-8pksf2x7.js";
27
+ import"./cli-wwbr5caw.js";
28
+ import"./cli-4pc0a18x.js";
29
29
  import"./cli-mm48br00.js";
30
- import"./cli-xddtv3yn.js";
31
- import"./cli-4sy4tytm.js";
32
- import"./cli-0xc5htv7.js";
33
- import"./cli-t0pb7qbs.js";
30
+ import"./cli-8pcq2et6.js";
31
+ import"./cli-e0kbye4s.js";
32
+ import"./cli-tztzrh6b.js";
33
+ import"./cli-dmm9jm9m.js";
34
+ import"./cli-e24fjyrb.js";
34
35
  import"./cli-k4tqwah4.js";
35
36
  import"./cli-9kfxkxmk.js";
36
37
  import"./cli-ns5fcnkz.js";
@@ -7,20 +7,21 @@ import {
7
7
  formatVerdict,
8
8
  rerunWithYes,
9
9
  resolvePromptArmSpec
10
- } from "./cli-hjm9151g.js";
11
- import"./cli-he8csgsr.js";
12
- import"./cli-daqkdnm1.js";
10
+ } from "./cli-8pksf2x7.js";
11
+ import"./cli-wwbr5caw.js";
12
+ import"./cli-4pc0a18x.js";
13
13
  import"./cli-mm48br00.js";
14
- import"./cli-xddtv3yn.js";
15
- import"./cli-4sy4tytm.js";
14
+ import"./cli-8pcq2et6.js";
15
+ import"./cli-e0kbye4s.js";
16
+ import"./cli-tztzrh6b.js";
16
17
  import {
17
18
  callControlAPI
18
- } from "./cli-0xc5htv7.js";
19
+ } from "./cli-dmm9jm9m.js";
19
20
  import {
20
21
  resolveApiTenantId,
21
22
  shellArg,
22
23
  validateConfig
23
- } from "./cli-t0pb7qbs.js";
24
+ } from "./cli-e24fjyrb.js";
24
25
  import {
25
26
  CliInputError
26
27
  } from "./cli-k4tqwah4.js";
@@ -11,14 +11,15 @@ import {
11
11
  runPromptSync,
12
12
  runPromptsCommand,
13
13
  writePromptSyncProgress
14
- } from "./cli-he8csgsr.js";
14
+ } from "./cli-wwbr5caw.js";
15
15
  import {
16
16
  promptSourceHashPayload
17
17
  } from "./cli-mm48br00.js";
18
- import"./cli-xddtv3yn.js";
19
- import"./cli-4sy4tytm.js";
20
- import"./cli-0xc5htv7.js";
21
- import"./cli-t0pb7qbs.js";
18
+ import"./cli-8pcq2et6.js";
19
+ import"./cli-e0kbye4s.js";
20
+ import"./cli-tztzrh6b.js";
21
+ import"./cli-dmm9jm9m.js";
22
+ import"./cli-e24fjyrb.js";
22
23
  import"./cli-k4tqwah4.js";
23
24
  import"./cli-9kfxkxmk.js";
24
25
  import"./cli-ns5fcnkz.js";
@@ -1,17 +1,18 @@
1
1
  import {
2
2
  selectTenantAndCreateKey
3
- } from "./cli-1z1sx8dg.js";
4
- import"./cli-77ekrx7d.js";
3
+ } from "./cli-5aa48ry5.js";
4
+ import"./cli-xcpwp0gb.js";
5
5
  import {
6
6
  loadAuthSession
7
- } from "./cli-pmzh2qjy.js";
7
+ } from "./cli-sejqvvhv.js";
8
8
  import {
9
9
  stringOption
10
- } from "./cli-xddtv3yn.js";
10
+ } from "./cli-8pcq2et6.js";
11
+ import"./cli-e0kbye4s.js";
11
12
  import {
12
13
  resolveIngestUrl,
13
14
  resolveModaBaseUrl
14
- } from "./cli-t0pb7qbs.js";
15
+ } from "./cli-e24fjyrb.js";
15
16
  import {
16
17
  CliAuthError
17
18
  } from "./cli-k4tqwah4.js";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  assertFlags
3
- } from "./cli-4sy4tytm.js";
3
+ } from "./cli-tztzrh6b.js";
4
4
  import {
5
5
  callControlAPI
6
- } from "./cli-0xc5htv7.js";
6
+ } from "./cli-dmm9jm9m.js";
7
7
  import {
8
8
  explicitOutputMode,
9
9
  resolveApiKey
10
- } from "./cli-t0pb7qbs.js";
10
+ } from "./cli-e24fjyrb.js";
11
11
  import {
12
12
  CliInputError
13
13
  } from "./cli-k4tqwah4.js";
@@ -3,14 +3,15 @@ import {
3
3
  pullSkills,
4
4
  runSkillSync,
5
5
  runSkillsCommand
6
- } from "./cli-mxfyq5nq.js";
6
+ } from "./cli-xx0pjzkh.js";
7
7
  import"./cli-qm5b1vck.js";
8
8
  import"./cli-rjav0vj1.js";
9
9
  import"./cli-hcwdz9sd.js";
10
- import"./cli-xddtv3yn.js";
11
- import"./cli-4sy4tytm.js";
12
- import"./cli-0xc5htv7.js";
13
- import"./cli-t0pb7qbs.js";
10
+ import"./cli-8pcq2et6.js";
11
+ import"./cli-e0kbye4s.js";
12
+ import"./cli-tztzrh6b.js";
13
+ import"./cli-dmm9jm9m.js";
14
+ import"./cli-e24fjyrb.js";
14
15
  import"./cli-k4tqwah4.js";
15
16
  import"./cli-ns5fcnkz.js";
16
17
  import"./cli-0v6na3yp.js";
@@ -1,24 +1,25 @@
1
1
  import {
2
2
  runSkillsCommand
3
- } from "./cli-mxfyq5nq.js";
3
+ } from "./cli-xx0pjzkh.js";
4
4
  import"./cli-qm5b1vck.js";
5
5
  import"./cli-rjav0vj1.js";
6
6
  import"./cli-hcwdz9sd.js";
7
7
  import {
8
8
  runPromptsCommand
9
- } from "./cli-he8csgsr.js";
9
+ } from "./cli-wwbr5caw.js";
10
10
  import"./cli-mm48br00.js";
11
11
  import {
12
12
  runHarnessCommand
13
- } from "./cli-vwpbspr5.js";
13
+ } from "./cli-d4cdczhe.js";
14
14
  import"./cli-xq45etg1.js";
15
15
  import"./cli-97fnztpy.js";
16
16
  import"./cli-cesds766.js";
17
17
  import"./cli-qpq84eee.js";
18
- import"./cli-xddtv3yn.js";
19
- import"./cli-4sy4tytm.js";
20
- import"./cli-0xc5htv7.js";
21
- import"./cli-t0pb7qbs.js";
18
+ import"./cli-8pcq2et6.js";
19
+ import"./cli-e0kbye4s.js";
20
+ import"./cli-tztzrh6b.js";
21
+ import"./cli-dmm9jm9m.js";
22
+ import"./cli-e24fjyrb.js";
22
23
  import {
23
24
  CliInputError
24
25
  } from "./cli-k4tqwah4.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.55.42",
3
+ "version": "1.55.44",
4
4
  "description": "CLI for Moda: production monitoring and evals for AI agents.",
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-10-18T02:37:16.940Z",
4
- "cli_version": "1.55.42",
3
+ "bundled_at": "2026-10-19T02:47:17.874Z",
4
+ "cli_version": "1.55.44",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-cloudflare-think",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: moda-cli
3
- version: 3.0.8
3
+ version: 3.0.10
4
4
  description: Investigate a production AI agent with the Moda CLI (`moda`) - find where something happened in real traces (traces search, then traces get), check agent health, rank what to fix (investigate, problems), and dig into tool failures, user frustration, and hallucinations. Also prompt registry sync/promote, replays, custom signals, and false-positive marking. Use when the user mentions Moda, production traces or conversations of their agent, agent failures, or asks what to fix.
5
5
  ---
6
6
 
@@ -20,8 +20,9 @@ in `references/`; read one only when the task needs it.
20
20
  only with an API key, so ready means `apiKeyCredential.verified` is `true`; a
21
21
  browser session from `moda auth login` alone is not enough. With no key, or
22
22
  a rejected one, ask the user to `export MODA_API_KEY=moda_sk_...` (or run
23
- `moda auth login --api-key`). Do not run `moda init` unprompted; it is an
24
- interactive wizard that writes to the repo.
23
+ `moda auth login --api-key`). `online.baseUrl` is where requests go and
24
+ `online.dataApi.tenantId` the tenant the key acts on. Do not run `moda init`
25
+ unprompted; it is an interactive wizard that writes to the repo.
25
26
  - Not installed: `npx -y -p @moda-ai/cli moda <command>`.
26
27
  - If a response carries `meta.update`, the installed CLI is out of date and
27
28
  may not match this guide; tell the user how to update
@@ -60,6 +61,8 @@ Details: `references/output-protocol.md`.
60
61
  | Where did X happen / traces about Y | `moda traces search "<words>"` (`--mode=keyword` for ids, error codes, exact strings; `--include-tool-io` for tool inputs/outputs) | `moda traces get <conversation_id> --msg-index=<message_index> --window=3` |
61
62
  | Search returned nothing | Read `.warnings` (degraded mode, reason). Try a shorter phrase, `--mode=keyword`, a wider `--time-range` | `moda traces --search="<words>"` |
62
63
  | Is the agent healthy? | `moda overview --days-back=7` | `moda investigate` |
64
+ | Did my change make it worse? | `moda overview --compare --since=<deploy time, ISO UTC>` (after the deploy against the same span before it; add `--agent-name=<sub-agent>` for one sub-agent) | `moda overview --series --since=<deploy time>` (per hour up to 2 days, else per day), `moda detections list --family=<f> --compare`, `moda problems get <problem_id> --trend` |
65
+ | Is X trending up? | `moda overview --compare --series --days-back=14` | `moda problems get <problem_id> --trend --days-back=30` (incidents per day) |
63
66
  | What to fix first? | `moda investigate` or `moda problems list` (free; both rank the same list the dashboard shows, `data.problemsView` names it) | `moda problems get <problem_id>` (`--view=dream` on a dream-grouped tenant), then `--evidence` / `--traces` |
64
67
  | Why is tool T failing? | `moda detections tools` | `moda detections tools <T>` (use each row's `anchor`) |
65
68
  | How often is tool T called / what is its schema? | `moda tools list` (every tool: calls, failed calls, success rate; `--sort=failure_rate`) | `moda tools get <T>` (registered schema, observed payload fields, usage over time, top failure categories) |
@@ -85,7 +85,7 @@ printf %s "$KEY" | moda auth login --api-key-stdin # key never touches argv
85
85
  moda auth login --api-key=<key> # inline; WARNS, key is visible in argv + shell history
86
86
  moda auth logout # revoke the session server-side, then delete it locally
87
87
  moda auth status # both credentials; exit 0 if either is usable, else 4
88
- moda auth status --online # additionally probe GET /api/cli/context
88
+ moda auth status --online # also probe /api/v1/data/whoami (+ /api/cli/context with a session)
89
89
  moda auth whoami # identity from the session, or from the API key
90
90
  moda auth token # print the raw bearer token + newline, nothing else
91
91
  curl -H "Authorization: Bearer $(moda auth token)" https://moda.dev/api/cli/context
@@ -156,12 +156,21 @@ two independent credentials and `auth` reports both:
156
156
  a rejected key gets `verified: false`, stops counting toward `authenticated`,
157
157
  and exits `4`; an unreachable API leaves it counting, so a blip never kills a
158
158
  good credential. With `--online` the payload adds
159
- `{ "online": { "reachable": <bool>, "ok": <bool>, "tenantCount": <n> } }`:
160
- `reachable: true` means any HTTP response arrived (even a 401/500), `ok`
161
- reflects HTTP success, and `tenantCount` is present only when the response
162
- was ok and carried a tenants array. A network failure or probe timeout
163
- (5 s) yields `reachable: false`; the exit code always follows local session
164
- validity only, never the probe.
159
+ `{ "online": { "baseUrl": <url>, "reachable": <bool>, "ok": <bool>,
160
+ "tenantCount"?: <n>, "session"?: {...}, "dataApi": { "reachable", "status",
161
+ "ok", "tenantId", "tenantName", "keyKind", "scope", "error"? } } }`.
162
+ `baseUrl` is where every request goes. `dataApi` probes
163
+ `GET /api/v1/data/whoami`, which the frontend proxy and the backend both
164
+ serve, so it confirms a base URL pointed straight at the backend and names
165
+ the key's tenant; without a key it is an unauthenticated reachability check
166
+ (a live Data API answers 401). `session` (only with a browser session)
167
+ probes `GET /api/cli/context`, a frontend-only route: a 404 there with a
168
+ reachable `dataApi` means the URL is the backend, and a warning says so.
169
+ `reachable: true` means any HTTP response arrived (even a 401/500); `ok`
170
+ means every credential present was accepted. A network failure or probe
171
+ timeout (5 s) yields `reachable: false`. A 403 for an unknown or
172
+ inaccessible tenant (`MODA_TENANT_ID`) keeps the key counted and adds a
173
+ warning with the tenant fix.
165
174
  - `moda auth token` writes exactly the bare token plus a trailing newline to
166
175
  stdout in **every** output mode — including piped/json/agent, because
167
176
  command substitution is a pipe — so `$(moda auth token)` composes safely
@@ -15,7 +15,7 @@ Loaded on demand from the moda-cli skill. This table can lag the CLI: `moda <com
15
15
  | `moda auth status` | Both credentials; exit `0` if either is usable, `4` if neither (expected, not an error) |
16
16
  | `moda auth token` | Print the raw bearer token for `$(moda auth token)` composition |
17
17
  | `moda traces search "<query>"` | **Message-grain semantic/keyword/hybrid search (incl. tool calls); primary content-discovery path** |
18
- | `moda overview` | Dashboard KPIs, top clusters, recent activity |
18
+ | `moda overview` | Dashboard KPIs, top clusters, recent activity. `--compare` adds `trends`: overview KPIs, per-unit rates, Problems and each detector family against the previous window of equal length; `--series` adds `[bucket, value]` arrays; `--since=<ISO> [--until=<ISO>]` sets the window (before/after a deploy); `--agent-name=<sub-agent>`; `--unit=conversation\|segment\|turn` |
19
19
  | `moda ask "<question>"` | Natural-language production/harness answer with evidence (exit `3` = degraded local fallback) |
20
20
  | `moda investigate` | Rank production issues (tool failures, frustrations, top Problems) with evidence + next commands |
21
21
  | `moda clusters` | Browse topic cluster hierarchy; `--search="q"` finds clusters by meaning, `--node-id=ID` resolves a deep link |
@@ -25,7 +25,7 @@ Loaded on demand from the moda-cli skill. This table can lag the CLI: `moda <com
25
25
  | `moda detections list --family=frustration` | User frustration detections with evidence (the frustration family of `emotions`, legacy field names) |
26
26
  | `moda detections list --family=emotion` | Multi-family emotion detections: frustration, disapproval, sadness, confusion, anxiety, trust, positive (`--family=F`, `--user-id`, `--conversation-id`, limit 1–20) |
27
27
  | `moda detections list --family=hallucination` | Grounding detections: contradicted/verified outputs, rule breakdown (`--kind=contradicted\|verified`, `--conversation-id=ID`) |
28
- | `moda detections tools` | Tool failure overview |
28
+ | `moda detections tools` | Tool failure overview (`--compare` adds `summary.compare`) |
29
29
  | `moda detections tools <tool_name>` | Per-tool failure breakdown + examples |
30
30
  | `moda tools list` | Tool catalog: every tool called in the window with calls, failed calls, success rate, traces, effect direction, last seen (`--sort=calls\|failure_rate\|failures\|traces\|last_seen`, `--search`, `--direction`, `--limit` 1-200, `--offset`) |
31
31
  | `moda tools get <tool_name>` | One tool: call stats, registered description and schema, observed payload fields, usage over time, top failure categories with the newest failure (`--days-back`, `--full`) |
@@ -35,6 +35,8 @@ Loaded on demand from the moda-cli skill. This table can lag the CLI: `moda <com
35
35
  | `moda review <kind> <target>` | Write: kinds `problem\|problem-detection\|quote\|tool\|laziness\|hallucination\|custom-signal`; `--reason` optional; problem-detection needs `--conversation-id` + `--attribution-id`; quote needs `--family`, `--turn`, `--quote`; tool needs `--tool` plus `--tool-use-id` or `--msg-index`; custom-signal needs `--conversation-id`; `--verdict=clear` removes a mark (not for problem) |
36
36
  | `moda reviews list [conversation_id]` | Current false-positive marks: all (`--kind=quote\|tool\|laziness\|hallucination\|custom-signal`, `--all` to include removed, `--limit`) or one trace with its problem detection reviews. Problem dismissals are read with `moda problems get <problem_id> --feedback`, detection reviews with `moda reviews list <conversation_id> --kind=problem-detection` |
37
37
  | `moda detections list --family=laziness` | Laziness detections per trace (`--days-back`, `--limit` 1-50, `--conversation-id`, `--family`) |
38
+ | `moda detections list --family=<f> --compare` | Adds `summary.compare`: the family's dashboard KPI tiles and per-trace rate for the list's `--days-back` window against the previous window of equal length (whole family; not with `--conversation-id`/`--user-id`) |
39
+ | `moda problems get <problem_id> --trend` | Incidents and users per day (`[date, value]`) and incidents/signals against the previous window of equal length (`--days-back`, default 30; no comparison above 45) |
38
40
  | `moda detections list --family=<f> --group-by=trace` | The dashboard's per-trace list for emotion, frustration and hallucination: totals count traces. Dashboard filters imply it: emotion/frustration `--search --status --elicitor --trajectory --signal --sort --order`; hallucination `--search --status --rule --source --sort --order`; laziness (always per trace) `--search --status --sort --order`. See detections.md 5a |
39
41
  | `moda problems linear <problem_id>` | The Problem's linked Linear issue (key, state, URL) or `link: null`; needs Linear connected (Integrations → Linear) |
40
42
  | `moda problems link <problem_id>` | Write: file the Problem as a Linear issue (`--team-id`, `--title`, `--priority=0-4`), or link an existing one with `--issue=ENG-123\|URL` (exclusive with the others). Idempotent per Problem |
@@ -14,6 +14,9 @@ Writes listed here accept `--dry-run`: it validates the call, prints the exact r
14
14
  | Traces list: Processed, Use case confidence, Indexed columns | `moda traces` rows: `processing_status`, `error_message`, `membership_score`, `indexed_at`; `moda traces get <id>`: `trace_state` |
15
15
  | Signals tabs: per-trace list, filters, sort | `moda detections list --family=<f> --group-by=trace` plus `--trajectory --elicitor --signal --status --search --sort --order` (emotion), `--rule --source` (hallucination) |
16
16
  | Settings: which detectors run | `moda overview` → `detectors` (read-only; switching one is dashboard-only) |
17
+ | Home / Analytics / Signals KPI cards: change against the previous period, sparklines, agent selector | `moda overview --compare [--series] [--since=<ISO> --until=<ISO>] [--agent-name=<sub-agent>] [--unit=turn]` |
18
+ | Detector tab KPI tiles with deltas | `moda detections list --family=<f> --compare` (`summary.compare`) |
19
+ | Problem history per day | `moda problems get <problem_id> --trend [--days-back=N]` |
17
20
  | Users page / user profile | `moda users [--window --cohort --sort --order --health-status]`, `moda user <id>` (`--cohort` takes `builtin:<key>`, a saved cohort id, or a bare built-in key) |
18
21
  | Cohort selector on Problems, Traces, Signals tabs, custom signals, Topics | `--cohort=builtin:<key>\|<saved id>` on `problems list`, `traces list`, `detections list` (emotion, frustration and hallucination read the per-trace list; laziness; custom_signal `--signal-id`), `detections tools`, `clusters` (`--time-range=all\|3d\|7d\|30d`, also with `--search`). Output says which cohort scoped it (summary line and `cohort` in the data). `traces search` has no cohort scope |
19
22
  | Cohort builder: list, fields, preview, compare, save, edit | `moda cohorts list [--include-archived]`, `get <ref>`, `fields`, `preview --file=<definition.json>`, `compare <a> <b> [--days=7\|30\|90]`, `create --name --file [--dry-run]`, `update <cohort_id> [--name --file --note] [--dry-run]` (archiving is dashboard-only) |
@@ -126,6 +126,26 @@ An empty detection list is not proof a trace is clean:
126
126
  pending or errored traces: a pending trace may already have some detections,
127
127
  but its detections may be incomplete.
128
128
 
129
+ ### 5c. Is a family getting worse? (`--compare`)
130
+
131
+ ```bash
132
+ moda detections list --family=frustration --compare --days-back=7
133
+ moda detections list --family=laziness:stub_body --compare
134
+ moda detections tools --compare --days-back=14
135
+ ```
136
+
137
+ `--compare` adds `summary.compare`, one more read (GET `/v1/data/trends`)
138
+ with the dashboard's KPI tiles for the family: `kpis` (for example emotion
139
+ analyzed traces, negative rate, negative detections, affected users and traces
140
+ per family) and `rate` (share of traces with a detection, the Signals rate
141
+ card; left out for `frustration` and a sub-family, since the card counts the
142
+ whole detector, and `notes` says so). Each figure is `{current, previous, delta, delta_pct, spike?}` for the
143
+ list's `--days-back` window against the window of equal length just before it.
144
+ It covers the whole family, so it rejects `--conversation-id` and `--user-id`;
145
+ other list filters do not narrow it. For a before/after-deploy window, or a
146
+ series per hour or day, use `moda overview --compare --since=<deploy time>`
147
+ and `--series` (see production-intelligence.md).
148
+
129
149
  ### 6. Tool failure investigation
130
150
 
131
151
  ```bash
@@ -42,7 +42,7 @@ Branch on the exit code; it is stable and cheaper than parsing the payload:
42
42
  | `1` | error — bad input, unknown id (`not_found`), access denied, API/network failure | Read `errors[0].code`; retry only `network_error` / 5xx `api_error`. For `not_found`, find a valid id first (the `cause` names the command). |
43
43
  | `3` | degraded — Moda Cloud was unavailable and the answer was synthesized from local Data API evidence (`ask`), or some sources failed to load (`investigate`/`failures`, named in `data.failedSources`) | Answer is present but lower-trust or partial; verify before acting, and never read a partial result as "no issues". |
44
44
  | `4` | auth required — no API key, or the API rejected it (HTTP 401) | Set `MODA_API_KEY` or run the `login_command` from the `auth` payload (`moda auth login --api-key`), then re-run. |
45
- | `5` | input required — a choice is needed that the non-interactive run could not answer | Pick a choice from the `input_request` payload and re-invoke the same command with that choice's `resume_flags` appended. |
45
+ | `5` | input required — a choice is needed that the non-interactive run could not answer (for example a tenant, or `--profile=local` for a loopback `MODA_BASE_URL` on a prod profile) | Pick a choice from the `input_request` payload and re-invoke the same command with that choice's `resume_flags` appended. |
46
46
  | `130` | cancelled | Treat as interrupted or closed pipe; do not retry blindly. |
47
47
 
48
48
  Exit codes `4` and `5` are recoverable-by-caller failures: the envelope's