@moda-ai/cli 1.55.4 → 1.55.6

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 CHANGED
@@ -127,6 +127,13 @@ Legacy aliases (same behavior, kept for existing scripts): `moda conversations`
127
127
  `moda cluster-conversations` = `moda cluster-traces`, `--conversations` = `--traces`
128
128
  (`moda problem`, `moda prompts ab`), `--signal=conversations` = `--signal=traces`.
129
129
 
130
+ Flag aliases: `moda context --message-index=N` = `--msg-index=N`, and
131
+ `moda users --limit=N` = `--page-size=N`. Each command rejects a flag it does
132
+ not take, and time windows differ by command: `--days-back=N` (a whole number,
133
+ 1-90) on the overview, Problem and detection commands, `--time-range` on
134
+ `traces`, `search` and `clusters`. Boolean flags accept `true`/`false`, `1`/`0`,
135
+ `yes`/`no` and `on`/`off`.
136
+
130
137
  Prompt management:
131
138
 
132
139
  ```bash
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  authFetch,
3
3
  getCachedLoginContext
4
- } from "./cli-t5v7mhvj.js";
4
+ } from "./cli-tw8jj190.js";
5
5
  import {
6
6
  CliAccessError,
7
7
  CliInputError,
@@ -9,13 +9,14 @@ import {
9
9
  embeddedTenantIdFromApiKey,
10
10
  getModaDir,
11
11
  loadCliConfig,
12
+ recordApiRequest,
12
13
  resolveApiKeyFromActiveProfile,
13
14
  resolveIngestUrl,
14
15
  resolveModaBaseUrl,
15
16
  resolveTenantIdFromActiveProfile,
16
17
  saveCliConfig,
17
18
  writeSecretJsonFile
18
- } from "./cli-zp7a97sp.js";
19
+ } from "./cli-mx9r1689.js";
19
20
 
20
21
  // src/init/tenant.ts
21
22
  import { randomUUID } from "node:crypto";
@@ -114,6 +115,7 @@ function bootstrapLabel(custom) {
114
115
  var embeddedTenantId = embeddedTenantIdFromApiKey;
115
116
  async function isStoredKeyLive(key, options = {}) {
116
117
  try {
118
+ recordApiRequest();
117
119
  const response = await fetch(`${resolveModaBaseUrl(options)}/api/v1/data/overview`, {
118
120
  method: "GET",
119
121
  headers: { "x-api-key": key },
@@ -21,7 +21,7 @@ import {
21
21
  safePromptEvidencePath,
22
22
  scanHarness,
23
23
  setPlatformTenantOverride
24
- } from "./cli-9v0c1xe1.js";
24
+ } from "./cli-gxmq4wmn.js";
25
25
  import {
26
26
  SKILL_SOURCE_HARNESS,
27
27
  SKILL_SOURCE_INTEGRATION_BUNDLED,
@@ -34,7 +34,7 @@ import {
34
34
  import {
35
35
  isAuthSessionValid,
36
36
  loadAuthSession
37
- } from "./cli-t5v7mhvj.js";
37
+ } from "./cli-tw8jj190.js";
38
38
  import {
39
39
  CliInputError,
40
40
  EXIT_ERROR,
@@ -45,6 +45,7 @@ import {
45
45
  getModaPaths,
46
46
  isLoopbackBaseUrl,
47
47
  loadProfileConfig,
48
+ recordApiRequest,
48
49
  resolveApiKey,
49
50
  resolveApiTenantId,
50
51
  resolveIngestUrl,
@@ -53,7 +54,7 @@ import {
53
54
  resolveTenantId,
54
55
  secretFilePathFromRef,
55
56
  validateConfig
56
- } from "./cli-zp7a97sp.js";
57
+ } from "./cli-mx9r1689.js";
57
58
  import {
58
59
  __commonJS,
59
60
  __require,
@@ -6659,7 +6660,7 @@ async function runPromptsCommand(input, legacyFlags, legacyProfileOptions) {
6659
6660
  await runPromptRegistryRead(subcommand, positionals, flags, profileOptions, context);
6660
6661
  break;
6661
6662
  default:
6662
- throw new Error(`Unknown prompts command '${subcommand}'. Use init, status, diff, migrate, sync, promote, ab, propose, list, show, proposals, usage, replay-runs, or decide.`);
6663
+ throw new CliInputError(`Unknown prompts command '${subcommand}'. Use init, status, diff, migrate, sync, promote, ab, propose, list, show, proposals, usage, replay-runs, or decide.`, undefined, "unknown_subcommand");
6663
6664
  }
6664
6665
  return 0;
6665
6666
  }
@@ -6668,7 +6669,7 @@ async function initPrompts(options = {}) {
6668
6669
  const manifestPath = resolve4(cwd, MANIFEST_PATH);
6669
6670
  const lockPath = resolve4(cwd, LOCK_PATH);
6670
6671
  if (existsSync2(MODA_DIR) && lstatSync(MODA_DIR).isSymbolicLink())
6671
- throw new Error(".moda must not be a symlink.");
6672
+ throw new CliInputError(".moda must not be a symlink.");
6672
6673
  mkdirSync(MODA_DIR, { recursive: true });
6673
6674
  let created = false;
6674
6675
  if (!existsSync2(MANIFEST_PATH)) {
@@ -6751,7 +6752,7 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
6751
6752
  const analyzedDefinitions = new Map;
6752
6753
  if (flags["no-analyze"] !== "true") {
6753
6754
  options.onProgress?.("Analyzing prompt sources before sync.");
6754
- const { analyzeHarnessPrompts } = await import("./harness-x6kaz47j.js");
6755
+ const { analyzeHarnessPrompts } = await import("./harness-7eabmv0v.js");
6755
6756
  const result = await analyzeHarnessPrompts({
6756
6757
  rootDir: process.cwd(),
6757
6758
  existing: established.flatMap((prompt) => prompt.sourceDefinition ? [prompt.sourceDefinition.source] : []),
@@ -6782,7 +6783,7 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
6782
6783
  const attribution = checkPromptAttribution(process.cwd(), prompts.map((prompt) => prompt.key));
6783
6784
  const brokenAttribution = attribution.references.filter((reference) => reference.status === "missing");
6784
6785
  if (brokenAttribution.length) {
6785
- throw new Error(`Prompt attribution keys are absent from this sync: ${brokenAttribution.map((reference) => `'${reference.key}' at ${reference.path}:${reference.line}`).join(", ")}. Map the referenced key to its verified source in .moda/prompts.yml, or update the runtime lookup to the intended mapped key. No prompt versions were uploaded.`);
6786
+ throw new CliInputError(`Prompt attribution keys are absent from this sync: ${brokenAttribution.map((reference) => `'${reference.key}' at ${reference.path}:${reference.line}`).join(", ")}. Map the referenced key to its verified source in .moda/prompts.yml, or update the runtime lookup to the intended mapped key. No prompt versions were uploaded.`);
6786
6787
  }
6787
6788
  for (const prompt of prompts) {
6788
6789
  const analyzed = analyzedDefinitions.get(prompt.key);
@@ -6793,7 +6794,7 @@ async function runPromptSyncExclusive(flags, profileOptions, options) {
6793
6794
  const previousLockHash = hashFile(LOCK_PATH);
6794
6795
  const missing = Object.keys(previous?.prompts ?? {}).filter((key) => !prompts.some((prompt) => prompt.key === key));
6795
6796
  if (missing.length && flags["allow-untrack"] !== "true") {
6796
- throw new Error(`Previously synced prompts are absent: ${missing.join(", ")}. Restore their sources, or use --allow-untrack to explicitly remove local tracking.`);
6797
+ throw new CliInputError(`Previously synced prompts are absent: ${missing.join(", ")}. Restore their sources, or use --allow-untrack to explicitly remove local tracking.`);
6797
6798
  }
6798
6799
  const sourceCommit = getGitCommit();
6799
6800
  const manifestHash = manifestUpdate === undefined ? originalManifestHash : sha256(manifestUpdate);
@@ -6888,11 +6889,11 @@ async function promotePrompt(positionals, flags, profileOptions, context) {
6888
6889
  const label = flags.label;
6889
6890
  const versionId = flags.version || flags["version-id"];
6890
6891
  if (!key)
6891
- throw new Error("Usage: moda prompts promote <prompt_key> --label=prod --version=<version_id>");
6892
+ throw new CliInputError("Usage: moda prompts promote <prompt_key> --label=prod --version=<version_id>", undefined, "missing_positional");
6892
6893
  if (!label)
6893
- throw new Error("Missing required flag: --label=dev|staging|prod");
6894
+ throw new CliInputError("Missing required flag: --label=dev|staging|prod");
6894
6895
  if (!versionId)
6895
- throw new Error("Missing required flag: --version=<version_id>");
6896
+ throw new CliInputError("Missing required flag: --version=<version_id>");
6896
6897
  const response = await callControlAPI(`/prompts/${encodeURIComponent(key)}/labels`, {
6897
6898
  method: "POST",
6898
6899
  body: JSON.stringify({ label, versionId })
@@ -6951,9 +6952,9 @@ function discoverManifestPrompts(manifest, extraSources = []) {
6951
6952
  for (const pattern of manifest.promptPaths) {
6952
6953
  const positive = pattern.startsWith("!") ? pattern.slice(1) : pattern;
6953
6954
  if (isAbsolute3(positive) || positive.split(/[\\/]/).includes(".."))
6954
- throw new Error(`Prompt pattern must stay inside the repository: ${pattern}`);
6955
+ throw new CliInputError(`Prompt pattern must stay inside the repository: ${pattern}`);
6955
6956
  if (!pattern.startsWith("!") && !import_fast_glob.default.isDynamicPattern(pattern) && !existsSync2(resolve4(root, pattern))) {
6956
- throw new Error(`Configured prompt file is missing: ${pattern}`);
6957
+ throw new CliInputError(`Configured prompt file is missing: ${pattern}`);
6957
6958
  }
6958
6959
  }
6959
6960
  const paths = import_fast_glob.default.sync(manifest.promptPaths, {
@@ -6979,7 +6980,7 @@ function discoverManifestPrompts(manifest, extraSources = []) {
6979
6980
  const seen = new Set;
6980
6981
  for (const prompt of prompts) {
6981
6982
  if (seen.has(prompt.key))
6982
- throw new Error(`Duplicate prompt key '${prompt.key}'. Give each source a unique stable key.`);
6983
+ throw new CliInputError(`Duplicate prompt key '${prompt.key}'. Give each source a unique stable key.`);
6983
6984
  seen.add(prompt.key);
6984
6985
  }
6985
6986
  return prompts.sort((a, b) => a.key.localeCompare(b.key));
@@ -7081,7 +7082,7 @@ async function migratePromptManifest(options = {}) {
7081
7082
  let addedSources = [];
7082
7083
  let pendingSources = [];
7083
7084
  if (options.discover) {
7084
- const { analyzeHarnessPrompts } = await import("./harness-x6kaz47j.js");
7085
+ const { analyzeHarnessPrompts } = await import("./harness-7eabmv0v.js");
7085
7086
  const result = await analyzeHarnessPrompts({
7086
7087
  rootDir: root,
7087
7088
  existing: manifest.sources,
@@ -7167,14 +7168,14 @@ function assertContainedSource(root, path) {
7167
7168
  const abs = resolve4(root, path);
7168
7169
  const rel = relative(realpathSync(root), realpathSync(abs));
7169
7170
  if (rel === ".." || rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`) || isAbsolute3(rel))
7170
- throw new Error(`Prompt source escapes repository: ${path}`);
7171
+ throw new CliInputError(`Prompt source escapes repository: ${path}`);
7171
7172
  let cursor = abs;
7172
7173
  while (cursor !== resolve4(root)) {
7173
7174
  if (lstatSync(cursor).isSymbolicLink())
7174
- throw new Error(`Prompt source uses a symlink: ${path}`);
7175
+ throw new CliInputError(`Prompt source uses a symlink: ${path}`);
7175
7176
  const next = dirname2(cursor);
7176
7177
  if (next === cursor)
7177
- throw new Error(`Prompt source escapes repository: ${path}`);
7178
+ throw new CliInputError(`Prompt source escapes repository: ${path}`);
7178
7179
  cursor = next;
7179
7180
  }
7180
7181
  }
@@ -7184,8 +7185,13 @@ function parsePromptFile(absPath) {
7184
7185
  if (absPath.endsWith(".json")) {
7185
7186
  const document = $parseDocument(raw, { uniqueKeys: true });
7186
7187
  if (document.errors.length)
7187
- throw new Error(`${sourcePath}: ${document.errors[0].message}`);
7188
- const parsed = JSON.parse(raw);
7188
+ throw new CliInputError(`${sourcePath}: ${document.errors[0].message}`);
7189
+ let parsed;
7190
+ try {
7191
+ parsed = JSON.parse(raw);
7192
+ } catch (err) {
7193
+ throw new CliInputError(`${sourcePath}: ${err instanceof Error ? err.message : String(err)}`);
7194
+ }
7189
7195
  const key2 = parsed.key || keyFromPath(sourcePath);
7190
7196
  const hasContent = typeof parsed.content === "string" && parsed.content.trim().length > 0;
7191
7197
  const hasSystemPrompt = typeof parsed.systemPrompt === "string" && parsed.systemPrompt.trim().length > 0;
@@ -7197,7 +7203,7 @@ function parsePromptFile(absPath) {
7197
7203
  return typeof content2 === "string" ? content2.trim().length > 0 : Array.isArray(content2) && content2.some((part) => part && typeof part === "object" && part.type === "text" && typeof part.text === "string" && part.text.trim().length > 0);
7198
7204
  });
7199
7205
  if (!hasContent && !hasSystemPrompt && !hasMessages && !hasTemplate) {
7200
- throw new Error(`${sourcePath}: prompt JSON requires non-empty content, systemPrompt, messages, or a text template.`);
7206
+ throw new CliInputError(`${sourcePath}: prompt JSON requires non-empty content, systemPrompt, messages, or a text template.`);
7201
7207
  }
7202
7208
  const content = hasContent ? parsed.content : hasSystemPrompt ? parsed.systemPrompt : hasMessages ? JSON.stringify(parsed.messages, null, 2) : parsed.template;
7203
7209
  return normalizePrompt({ ...parsed, key: key2, name: parsed.name || key2, sourcePath, content });
@@ -7239,23 +7245,23 @@ function readManifest(root = process.cwd()) {
7239
7245
  assertContainedSource(root, MANIFEST_PATH);
7240
7246
  const doc = $parseDocument(readFileSync(resolve4(root, MANIFEST_PATH), "utf8"), { uniqueKeys: true });
7241
7247
  if (doc.errors.length)
7242
- throw new Error(`Invalid ${MANIFEST_PATH}: ${doc.errors[0].message}`);
7248
+ throw new CliInputError(`Invalid ${MANIFEST_PATH}: ${doc.errors[0].message}`);
7243
7249
  const parsed = doc.toJS({ maxAliasCount: 20 });
7244
7250
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed) || ![1, 2].includes(parsed.version))
7245
- throw new Error(`${MANIFEST_PATH} requires version: 1 or 2.`);
7251
+ throw new CliInputError(`${MANIFEST_PATH} requires version: 1 or 2.`);
7246
7252
  if (Object.keys(parsed).some((key) => !["version", "prompt_paths", "sources"].includes(key)))
7247
- throw new Error("Unknown prompt manifest field. Use version, prompt_paths, and sources.");
7253
+ throw new CliInputError("Unknown prompt manifest field. Use version, prompt_paths, and sources.");
7248
7254
  if (parsed.prompt_paths !== undefined && (!Array.isArray(parsed.prompt_paths) || parsed.prompt_paths.some((p) => typeof p !== "string" || !p.trim())))
7249
- throw new Error("prompt_paths must be an array of nonempty patterns.");
7255
+ throw new CliInputError("prompt_paths must be an array of nonempty patterns.");
7250
7256
  if (parsed.sources !== undefined && !Array.isArray(parsed.sources))
7251
- throw new Error("sources must be an array of prompt source descriptors.");
7257
+ throw new CliInputError("sources must be an array of prompt source descriptors.");
7252
7258
  const sources = parsed.sources ?? [];
7253
7259
  const seen = new Set;
7254
7260
  for (const source of sources) {
7255
7261
  if (!source || typeof source.key !== "string" || !source.key.trim() || typeof source.path !== "string" || !source.path.trim())
7256
- throw new Error("Every prompt source requires a stable key and path.");
7262
+ throw new CliInputError("Every prompt source requires a stable key and path.");
7257
7263
  if (seen.has(source.key))
7258
- throw new Error(`Duplicate prompt source key '${source.key}'.`);
7264
+ throw new CliInputError(`Duplicate prompt source key '${source.key}'.`);
7259
7265
  seen.add(source.key);
7260
7266
  }
7261
7267
  return { version: parsed.version, promptPaths: parsed.prompt_paths ?? (parsed.version === 1 ? DEFAULT_PROMPT_PATHS : []), sources };
@@ -7354,7 +7360,7 @@ function mergePromptSources(existingSources, sources) {
7354
7360
  for (const source of sources) {
7355
7361
  const existing = merged.get(source.key);
7356
7362
  if (existing && stableJson(existing) !== stableJson(source)) {
7357
- throw new Error(`Harness source '${source.key}' conflicts with an existing mapping. Edit ${MANIFEST_PATH} explicitly to change its source; no mappings were imported.`);
7363
+ throw new CliInputError(`Harness source '${source.key}' conflicts with an existing mapping. Edit ${MANIFEST_PATH} explicitly to change its source; no mappings were imported.`);
7358
7364
  }
7359
7365
  merged.set(source.key, source);
7360
7366
  }
@@ -7364,7 +7370,7 @@ function atomicWrite(path, content) {
7364
7370
  mkdirSync(dirname2(path), { recursive: true });
7365
7371
  assertContainedSource(process.cwd(), dirname2(path));
7366
7372
  if (existsSync2(path) && lstatSync(path).isSymbolicLink())
7367
- throw new Error(`Refusing to overwrite symlink: ${path}`);
7373
+ throw new CliInputError(`Refusing to overwrite symlink: ${path}`);
7368
7374
  const temp = `${path}.${process.pid}.tmp`;
7369
7375
  try {
7370
7376
  writeFileSync2(temp, content, { flag: "wx" });
@@ -7409,7 +7415,7 @@ function writeLock(prompts, synced) {
7409
7415
  }
7410
7416
  function assertReplayablePrompt(prompt) {
7411
7417
  if (prompt.sourceDefinition && ["builder", "template"].includes(prompt.sourceDefinition.kind)) {
7412
- throw new Error(`Prompt '${prompt.key}' is a ${prompt.sourceDefinition.kind}. Run its renderer with controlled inputs; source code cannot be replayed as final instructions.`);
7418
+ throw new CliInputError(`Prompt '${prompt.key}' is a ${prompt.sourceDefinition.kind}. Run its renderer with controlled inputs; source code cannot be replayed as final instructions.`);
7413
7419
  }
7414
7420
  }
7415
7421
  function parseFrontmatter(raw) {
@@ -7636,6 +7642,7 @@ async function fetchSkills(status) {
7636
7642
  const timeout = setTimeout(() => controller.abort(), SKILLS_REQUEST_TIMEOUT_MS);
7637
7643
  let response;
7638
7644
  try {
7645
+ recordApiRequest();
7639
7646
  response = await fetch(url, {
7640
7647
  headers: { Authorization: `Bearer ${apiKey}` },
7641
7648
  signal: controller.signal
@@ -7673,6 +7680,7 @@ async function skillRunsRequest(url) {
7673
7680
  const timeout = setTimeout(() => controller.abort(), SKILLS_REQUEST_TIMEOUT_MS);
7674
7681
  let response;
7675
7682
  try {
7683
+ recordApiRequest();
7676
7684
  response = await fetch(url, {
7677
7685
  headers: { Authorization: `Bearer ${apiKey}` },
7678
7686
  signal: controller.signal
@@ -8281,6 +8289,7 @@ async function generateSkills(flags) {
8281
8289
  };
8282
8290
  if (tenantId)
8283
8291
  headers["X-Moda-Tenant-Id"] = tenantId;
8292
+ recordApiRequest();
8284
8293
  const response = await fetch(endpoint, {
8285
8294
  method: "POST",
8286
8295
  headers,
@@ -9044,6 +9053,7 @@ async function cloudDataFlowChecks(ingestUrl, apiKey) {
9044
9053
  async function ingestReachabilityCheck(ingestUrl, apiKey) {
9045
9054
  const url = `${ingestUrl.replace(/\/+$/, "")}/`;
9046
9055
  try {
9056
+ recordApiRequest();
9047
9057
  const response = await fetch(url, {
9048
9058
  method: "GET",
9049
9059
  headers: { Authorization: `Bearer ${apiKey}` },
@@ -1,4 +1,5 @@
1
1
  import {
2
+ CliAccessError,
2
3
  CliInputError,
3
4
  CliInputRequiredError,
4
5
  DEFAULT_MAX_INVENTORY_FILES,
@@ -16,6 +17,7 @@ import {
16
17
  isSkippedPathPrefix,
17
18
  isTextFile,
18
19
  packSnapshot,
20
+ recordApiRequest,
19
21
  redactDeep,
20
22
  redactString,
21
23
  resolveApiKey,
@@ -25,7 +27,7 @@ import {
25
27
  resolveTenantId,
26
28
  shouldInventoryFile,
27
29
  stringOption
28
- } from "./cli-zp7a97sp.js";
30
+ } from "./cli-mx9r1689.js";
29
31
  import {
30
32
  __commonJS,
31
33
  __require,
@@ -177929,7 +177931,6 @@ var REQUEST_TIMEOUT_MS = 30000;
177929
177931
  var MAX_RETRIES = 3;
177930
177932
  var INITIAL_BACKOFF_MS = 500;
177931
177933
  var NETWORK_RETRY_DELAY_MS = 1000;
177932
- var apiRequestCount = 0;
177933
177934
 
177934
177935
  class ApiError extends Error {
177935
177936
  statusCode;
@@ -178004,7 +178005,6 @@ function isRetryable(status) {
178004
178005
  return status === 429 || status >= 500;
178005
178006
  }
178006
178007
  async function callDataAPI(endpoint, init = { method: "GET" }, requestOptions = {}) {
178007
- apiRequestCount++;
178008
178008
  const url = `${resolveModaBaseUrl()}/api/v1/data${endpoint}`;
178009
178009
  return callModaAPI(url, init, {}, requestOptions);
178010
178010
  }
@@ -178028,6 +178028,7 @@ async function callModaAPI(url, options, profileOptions = {}, requestOptions = {
178028
178028
  const controller = new AbortController;
178029
178029
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
178030
178030
  try {
178031
+ recordApiRequest();
178031
178032
  const response = await fetch(url, {
178032
178033
  ...options,
178033
178034
  headers: {
@@ -178073,12 +178074,6 @@ async function callModaAPI(url, options, profileOptions = {}, requestOptions = {
178073
178074
  }
178074
178075
  throw new NetworkError(`Request retries exhausted: ${url}`);
178075
178076
  }
178076
- function getApiRequestCount() {
178077
- return apiRequestCount;
178078
- }
178079
- function resetApiRequestCount() {
178080
- apiRequestCount = 0;
178081
- }
178082
178077
 
178083
178078
  // src/parity-commands.ts
178084
178079
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
@@ -178200,6 +178195,7 @@ async function runUsersCommand(context) {
178200
178195
  "window",
178201
178196
  "page",
178202
178197
  "page-size",
178198
+ "limit",
178203
178199
  "search",
178204
178200
  "cohort",
178205
178201
  "sort",
@@ -178214,12 +178210,15 @@ async function runUsersCommand(context) {
178214
178210
  if (context.positionals.length > 0) {
178215
178211
  throw new CliInputError(`'moda users' takes no arguments. For one user use 'moda user <user_id>'.`);
178216
178212
  }
178213
+ if (flags.limit !== undefined && flags["page-size"] !== undefined && flags.limit !== flags["page-size"]) {
178214
+ throw new CliInputError(`Got --limit=${flags.limit} and --page-size=${flags["page-size"]}; they are the same flag for 'moda users', so nothing was sent.`, "Pass only one of --limit / --page-size.", "flag_conflict");
178215
+ }
178217
178216
  requireKey(context);
178218
178217
  const tenantId = requireTenant(context);
178219
178218
  const query = new URLSearchParams(compact({
178220
178219
  window: oneOf("window", flags.window, WINDOWS),
178221
178220
  page: flags.page,
178222
- pageSize: flags["page-size"],
178221
+ pageSize: flags["page-size"] ?? flags.limit,
178223
178222
  search: flags.search,
178224
178223
  cohort: oneOf("cohort", flags.cohort, USER_COHORTS),
178225
178224
  sortBy: oneOf("sort", flags.sort, USER_SORTS),
@@ -178495,19 +178494,36 @@ async function runSignalsManagementCommand(sub, context) {
178495
178494
  if (!body.detector)
178496
178495
  throw new CliInputError('--file must contain {"detector": {...}} (optionally scope).');
178497
178496
  start();
178498
- await send(context, "control", "POST", `${base()}/detector-preview`, {
178499
- ...body,
178500
- ...compact({
178501
- conversationIds: csv(flags["conversation-ids"]),
178502
- maxExamples: flags["max-examples"] !== undefined ? Number(flags["max-examples"]) : undefined
178503
- })
178504
- });
178497
+ try {
178498
+ await send(context, "control", "POST", `${base()}/detector-preview`, {
178499
+ ...body,
178500
+ ...compact({
178501
+ conversationIds: csv(flags["conversation-ids"]),
178502
+ maxExamples: flags["max-examples"] !== undefined ? Number(flags["max-examples"]) : undefined
178503
+ })
178504
+ });
178505
+ } catch (error) {
178506
+ if (isTenantFeatureNotFound(error)) {
178507
+ throw new CliAccessError("The signal builder is not enabled for this tenant, so `moda signals preview` is unavailable (the API answered 404).", { guidance: "Ask Moda to enable the signal-builder feature for this tenant. Other `moda signals` subcommands still work." });
178508
+ }
178509
+ throw error;
178510
+ }
178505
178511
  return { exitCode: 0 };
178506
178512
  }
178507
178513
  default:
178508
178514
  throw unknownSignalsSubcommandError(sub, args);
178509
178515
  }
178510
178516
  }
178517
+ function isTenantFeatureNotFound(error) {
178518
+ if (!(error instanceof ApiError) || error.statusCode !== 404)
178519
+ return false;
178520
+ try {
178521
+ const body = JSON.parse(error.responseBody);
178522
+ return body.message === "Not Found" && body.error === undefined;
178523
+ } catch {
178524
+ return false;
178525
+ }
178526
+ }
178511
178527
  var SIGNALS_MANAGEMENT_SUBCOMMANDS = new Set([
178512
178528
  "draft",
178513
178529
  "create",
@@ -180841,6 +180857,7 @@ async function remoteAnalyzeFetch(url, apiKey, init, timeoutMs) {
180841
180857
  const timeout = setTimeout(() => controller.abort(), timeoutMs);
180842
180858
  let response;
180843
180859
  try {
180860
+ recordApiRequest();
180844
180861
  response = await fetch(url, {
180845
180862
  ...init,
180846
180863
  headers: { Authorization: `Bearer ${apiKey}`, ...init.headers ?? {} },
@@ -184897,7 +184914,7 @@ async function runHarnessCommand(context) {
184897
184914
  throw new CliInputError("Focused remote prompt analysis is not supported yet.", "Use local --analyst=claude|codex|cursor|local-scan, or drop --concern for a full remote report.");
184898
184915
  }
184899
184916
  if (context.flags["github-actions"] === "true") {
184900
- const { runGithubActionsAnalyze } = await import("./harness-github-actions-5syjp1sz.js");
184917
+ const { runGithubActionsAnalyze } = await import("./harness-github-actions-s10y3kpz.js");
184901
184918
  const result = await runGithubActionsAnalyze(rootDir, {
184902
184919
  writeReport: (report2) => {
184903
184920
  const normalized = normalizeHarnessReport(report2);
@@ -188942,4 +188959,4 @@ function hashText(text) {
188942
188959
  return createHash4("sha256").update(text).digest("hex");
188943
188960
  }
188944
188961
 
188945
- export { isPlatformApiKey, setPlatformTenantOverride, apiKeyHeaders, ApiError, callDataAPI, callControlAPI, getApiRequestCount, resetApiRequestCount, $isSeq, $parseDocument, $stringify, require_typescript, inspectCodeSelectors, inspectPythonSelectors, resolvePromptSource, backfillPromptSource, HARNESS_REPORT_PATH, HARNESS_REPORT_APPROVAL_PATH, HARNESS_ANALYZE_CONCERNS, validateHarnessReport, readHarnessReport, normalizeHarnessReport, hashHarnessReport, hashStableJson, assertFlags, assertPositionals, runUsersCommand, runUserCommand, runProblemReopenCommand, runSignalsManagementCommand, SIGNALS_MANAGEMENT_SUBCOMMANDS, unknownSignalsSubcommandError, shouldColorTerminalOutput, terminalStyles, stripAnsi2 as stripAnsi, renderMarkdownForTerminal, startRemoteAnalyze, pollRemoteAnalyzeUntilDone, isRecoverableRemoteRun, checkPromptMapping, safePromptEvidencePath, discoverHarnessPromptSources, renderDoctorHuman, renderStatusHuman, renderInitBanner, renderInitStep, renderInitNote, renderInitSetupPlan, renderInitComplete, runHarnessCommand, writeHarnessAnalyzeResult, emitWrittenArtifacts, analyzeHarnessPrompts, repairExternalAnalystReport, deriveRelationshipsFromGraph, mergeDeterministicArtifacts, describeCodingAgentEvent, analystEventProgress, isRetryableExternalAnalystFailure, sleep, summarizeInvalidAnalystReport, isDeterministicLocalAnalyst, resolveHarnessAnalyzeConcern, detectAvailableCodingAgents, codingAgentDisplayName, runCodingAgent, resolveAnalystModel, renderExternalHarnessAnalystPrompt, CONCERN_CANDIDATE_KINDS, SEED_CANDIDATE_KIND_LABELS, renderAnalystCandidateLines, renderAnalystSeedLines, truncateText, scanHarness, summarizeHarness, buildSourceSnapshot, writeHumanProgress, readHarnessSyncState, describeSyncIdentity, describeSyncTenantLabel, findFresherHarnessReports, writeHarnessArtifacts, assertHarnessGraphSyncQuality, graphForSync, inventoryFiles, numericEnv, numericEnvAllowZero, computeDeterministicFloor, stableId2 as stableId, titleFromId2 as titleFromId, promptNameFromPath };
188962
+ export { isPlatformApiKey, setPlatformTenantOverride, apiKeyHeaders, ApiError, callDataAPI, callControlAPI, $isSeq, $parseDocument, $stringify, require_typescript, inspectCodeSelectors, inspectPythonSelectors, resolvePromptSource, backfillPromptSource, HARNESS_REPORT_PATH, HARNESS_REPORT_APPROVAL_PATH, HARNESS_ANALYZE_CONCERNS, validateHarnessReport, readHarnessReport, normalizeHarnessReport, hashHarnessReport, hashStableJson, assertFlags, assertPositionals, runUsersCommand, runUserCommand, runProblemReopenCommand, runSignalsManagementCommand, SIGNALS_MANAGEMENT_SUBCOMMANDS, unknownSignalsSubcommandError, shouldColorTerminalOutput, terminalStyles, stripAnsi2 as stripAnsi, renderMarkdownForTerminal, startRemoteAnalyze, pollRemoteAnalyzeUntilDone, isRecoverableRemoteRun, checkPromptMapping, safePromptEvidencePath, discoverHarnessPromptSources, renderDoctorHuman, renderStatusHuman, renderInitBanner, renderInitStep, renderInitNote, renderInitSetupPlan, renderInitComplete, runHarnessCommand, writeHarnessAnalyzeResult, emitWrittenArtifacts, analyzeHarnessPrompts, repairExternalAnalystReport, deriveRelationshipsFromGraph, mergeDeterministicArtifacts, describeCodingAgentEvent, analystEventProgress, isRetryableExternalAnalystFailure, sleep, summarizeInvalidAnalystReport, isDeterministicLocalAnalyst, resolveHarnessAnalyzeConcern, detectAvailableCodingAgents, codingAgentDisplayName, runCodingAgent, resolveAnalystModel, renderExternalHarnessAnalystPrompt, CONCERN_CANDIDATE_KINDS, SEED_CANDIDATE_KIND_LABELS, renderAnalystCandidateLines, renderAnalystSeedLines, truncateText, scanHarness, summarizeHarness, buildSourceSnapshot, writeHumanProgress, readHarnessSyncState, describeSyncIdentity, describeSyncTenantLabel, findFresherHarnessReports, writeHarnessArtifacts, assertHarnessGraphSyncQuality, graphForSync, inventoryFiles, numericEnv, numericEnvAllowZero, computeDeterministicFloor, stableId2 as stableId, titleFromId2 as titleFromId, promptNameFromPath };
@@ -647,6 +647,18 @@ function validateConfig() {
647
647
  }
648
648
  }
649
649
 
650
+ // src/api-request-count.ts
651
+ var apiRequestCount = 0;
652
+ function recordApiRequest() {
653
+ apiRequestCount++;
654
+ }
655
+ function getApiRequestCount() {
656
+ return apiRequestCount;
657
+ }
658
+ function resetApiRequestCount() {
659
+ apiRequestCount = 0;
660
+ }
661
+
650
662
  // ../../packages/harness-snapshot/src/policy.ts
651
663
  var MAX_SCANNED_FILE_BYTES = 512 * 1024;
652
664
  var DEFAULT_MAX_INVENTORY_FILES = 12000;
@@ -1535,7 +1547,7 @@ function formatValidationMessage(error) {
1535
1547
  const message = typeof record.message === "string" ? record.message : "is invalid";
1536
1548
  return `${path}: ${message}`;
1537
1549
  });
1538
- return `Validation error — ${parts.join("; ")}`;
1550
+ return `Validation error — ${[...new Set(parts)].join("; ")}`;
1539
1551
  }
1540
1552
  function errorToAgentError(error) {
1541
1553
  if (error instanceof CliInputRequiredError) {
@@ -1863,6 +1875,22 @@ function extractSuggestedCommands(text) {
1863
1875
  function flagEnabled(flags, key) {
1864
1876
  return flags[key] === "true";
1865
1877
  }
1878
+ var BOOLEAN_TRUE_WORDS = new Set(["true", "yes", "1", "on"]);
1879
+ var BOOLEAN_FALSE_WORDS = new Set(["false", "no", "0", "off"]);
1880
+ function parseBooleanFlagValue(value) {
1881
+ if (typeof value === "boolean")
1882
+ return value;
1883
+ if (typeof value === "number")
1884
+ return value === 1 ? true : value === 0 ? false : undefined;
1885
+ if (typeof value !== "string")
1886
+ return;
1887
+ const text = value.trim().toLowerCase();
1888
+ if (BOOLEAN_TRUE_WORDS.has(text))
1889
+ return true;
1890
+ if (BOOLEAN_FALSE_WORDS.has(text))
1891
+ return false;
1892
+ return;
1893
+ }
1866
1894
  function stringOption(value) {
1867
1895
  if (value === undefined || value === null)
1868
1896
  return;
@@ -1907,4 +1935,4 @@ function createCommandContext(input, options = {}) {
1907
1935
  };
1908
1936
  }
1909
1937
 
1910
- export { PROD_BASE_URL, DEFAULT_INGEST_URL, getModaPaths, getModaDir, isLoopbackBaseUrl, assertValidProfileName, describeBaseUrlSource, loadProfileConfig, saveProfileConfig, getActiveProfile, setActiveProfile, upsertProfile, clearStoredApiKeyCredentials, secretFilePathFromRef, resolveSecretReference, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, ensureSecretFilePermissions, writeSecretJsonFile, CliInputError, CliAccessError, CliInputRequiredError, CliAuthError, CliCancellationError, isNetworkLikeError, isApiLikeError, VERSION, MODA_BASE_URL, loadCliConfig, saveCliConfig, resolveApiKey, embeddedTenantIdFromApiKey, resolveIngestUrl2 as resolveIngestUrl, resolveTenantId, resolveApiTenantId, resolveModaBaseUrl, validateConfig, MAX_SCANNED_FILE_BYTES, DEFAULT_MAX_INVENTORY_FILES, DEFAULT_MAX_INVENTORY_TEXT_BYTES, MAX_RECORDED_SKIPS, SKIPPED_DIRS, isSkippedDir, isSkippedPathPrefix, isEvalLikePath, shouldInventoryFile, isTextFile, isSecretishPath, SECRET_KEYS, redactDeep, redactString, packSnapshot, EXIT_OK, EXIT_ERROR, EXIT_DEGRADED, EXIT_AUTH_REQUIRED, EXIT_INPUT_REQUIRED, EXIT_CANCELLED, AGENT_ERROR_CODES, wasOutputCancelled, safeWriteStdout, safeWriteStderr, explicitOutputMode, resolveOutputMode, createCliOutput, agentEnvelopeFits, agentMaxOutputBytes, stringOption, createCommandContext };
1938
+ export { PROD_BASE_URL, DEFAULT_INGEST_URL, getModaPaths, getModaDir, isLoopbackBaseUrl, assertValidProfileName, describeBaseUrlSource, loadProfileConfig, saveProfileConfig, getActiveProfile, setActiveProfile, upsertProfile, clearStoredApiKeyCredentials, secretFilePathFromRef, resolveSecretReference, resolveApiKeyFromActiveProfile, resolveTenantIdFromActiveProfile, ensureSecretFilePermissions, writeSecretJsonFile, CliInputError, CliAccessError, CliInputRequiredError, CliAuthError, CliCancellationError, isNetworkLikeError, isApiLikeError, VERSION, MODA_BASE_URL, loadCliConfig, saveCliConfig, resolveApiKey, embeddedTenantIdFromApiKey, resolveIngestUrl2 as resolveIngestUrl, resolveTenantId, resolveApiTenantId, resolveModaBaseUrl, validateConfig, recordApiRequest, getApiRequestCount, resetApiRequestCount, MAX_SCANNED_FILE_BYTES, DEFAULT_MAX_INVENTORY_FILES, DEFAULT_MAX_INVENTORY_TEXT_BYTES, MAX_RECORDED_SKIPS, SKIPPED_DIRS, isSkippedDir, isSkippedPathPrefix, isEvalLikePath, shouldInventoryFile, isTextFile, isSecretishPath, SECRET_KEYS, redactDeep, redactString, packSnapshot, EXIT_OK, EXIT_ERROR, EXIT_DEGRADED, EXIT_AUTH_REQUIRED, EXIT_INPUT_REQUIRED, EXIT_CANCELLED, AGENT_ERROR_CODES, wasOutputCancelled, safeWriteStdout, safeWriteStderr, explicitOutputMode, resolveOutputMode, createCliOutput, agentEnvelopeFits, agentMaxOutputBytes, parseBooleanFlagValue, stringOption, createCommandContext };
@@ -3,11 +3,12 @@ import {
3
3
  CliInputError,
4
4
  getModaPaths,
5
5
  loadProfileConfig,
6
+ recordApiRequest,
6
7
  resolveModaBaseUrl,
7
8
  saveProfileConfig,
8
9
  secretFilePathFromRef,
9
10
  writeSecretJsonFile
10
- } from "./cli-zp7a97sp.js";
11
+ } from "./cli-mx9r1689.js";
11
12
  import {
12
13
  __require
13
14
  } from "./cli-0v6na3yp.js";
@@ -179,6 +180,7 @@ function postCliSessionEndpoint(path, token, timeoutMs, opts = {}) {
179
180
  return;
180
181
  }
181
182
  const requestFn = url.protocol === "https:" ? httpsRequest : httpRequest;
183
+ recordApiRequest();
182
184
  const req = requestFn(url, {
183
185
  method: "POST",
184
186
  headers: { Authorization: `Bearer ${token}` },
@@ -345,6 +347,7 @@ async function connectWithCode(code, options = {}) {
345
347
  }
346
348
  let response;
347
349
  try {
350
+ recordApiRequest();
348
351
  response = await fetch(`${resolveModaBaseUrl(options)}/api/cli/auth/connect`, {
349
352
  method: "POST",
350
353
  headers: { "Content-Type": "application/json" },
@@ -594,6 +597,7 @@ async function login(screen = "login", options = {}) {
594
597
  async function exchangeCodeForSession(code) {
595
598
  let response;
596
599
  try {
600
+ recordApiRequest();
597
601
  response = await fetch(`${resolveModaBaseUrl(storageOptions)}/api/cli/auth/exchange`, {
598
602
  method: "POST",
599
603
  headers: { "Content-Type": "application/json" },
@@ -798,6 +802,7 @@ async function authFetch(path, options = {}, authOptions = {}) {
798
802
  let url = `${baseUrl}${path}`;
799
803
  const MAX_REDIRECTS = 5;
800
804
  for (let hop = 0;; hop++) {
805
+ recordApiRequest();
801
806
  const response = await fetch(url, { ...options, headers, redirect: "manual" });
802
807
  if (response.status < 300 || response.status >= 400) {
803
808
  if (response.status === 401 && isModaCtSession(session)) {