@moda-ai/cli 1.13.3 → 1.13.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -238,9 +238,11 @@ Init flags:
238
238
  --yes Accept all defaults (non-interactive)
239
239
  --analyst=auto|claude|codex|cursor|local-scan
240
240
  Choose harness analyst adapter
241
- --remote Run harness analysis server-side (Moda-hosted):
242
- upload a safe source snapshot; no local agent
243
- (also MODA_HARNESS_REMOTE=1)
241
+ --remote Run harness analysis server-side (Moda-hosted,
242
+ the default): upload a safe source snapshot; no
243
+ local coding agent required
244
+ --local Run harness analysis with a LOCAL coding agent
245
+ instead of server-side (opt out of remote)
244
246
  --tui Show full-screen analyst dashboard in human TTYs
245
247
  --no-tui Show raw external analyst output
246
248
  --sdk-integrate Run your coding agent to install the Moda SDK and instrument LLM calls
@@ -27,7 +27,18 @@ export function isRemoteHarnessAnalyze(context) {
27
27
  // The CLI normalizes numeric flag values to numbers, so `--remote=1` arrives
28
28
  // as the number 1 rather than the string '1'; accept every truthy spelling.
29
29
  const flag = context.flags.remote;
30
- if (flag === true || flag === 1 || flag === 'true' || flag === '1' || flag === 'yes')
30
+ // An explicit local opt-out (`--local` / `--no-remote` / `--remote=false|0`)
31
+ // wins over the `MODA_HARNESS_REMOTE` env, so callers that force local — the
32
+ // `moda init` local path and its remote-failure fallback — stay local even
33
+ // when the env is set. `no-remote` may arrive kebab- or snake-cased depending
34
+ // on the caller (real CLI parsing snake-cases; internal callers do not).
35
+ const noRemote = context.flags['no-remote'] ?? context.flags.no_remote;
36
+ const local = context.flags.local;
37
+ const isTruthy = (v) => v === true || v === 1 || v === 'true' || v === '1' || v === 'yes';
38
+ const isFalsy = (v) => v === false || v === 0 || v === 'false' || v === '0' || v === 'no';
39
+ if (isFalsy(flag) || isTruthy(noRemote) || isTruthy(local))
40
+ return false;
41
+ if (isTruthy(flag))
31
42
  return true;
32
43
  const env = (context.env.MODA_HARNESS_REMOTE ?? '').trim().toLowerCase();
33
44
  return env !== '' && env !== '0' && env !== 'false';
@@ -28,4 +28,10 @@ export interface InitRunOptions {
28
28
  onEvent?: (event: InitProgressEvent) => void;
29
29
  }
30
30
  export declare function runInit(flags: Record<string, unknown>, options?: InitRunOptions): Promise<InitResult>;
31
+ /**
32
+ * Onboarding runs harness discovery SERVER-SIDE by default so the customer
33
+ * needs no local coding agent. Opt out with `--local`, `--no-remote`, or
34
+ * `--remote=false` to use a local coding-agent analyst instead.
35
+ */
36
+ export declare function resolveHarnessRemote(flags: Record<string, unknown>): boolean;
31
37
  //# sourceMappingURL=index.d.ts.map
@@ -97,17 +97,14 @@ export async function runInit(flags, options = {}) {
97
97
  log(renderInitNote(`Active workspace: ${tenantName}`));
98
98
  emit('workspace', `Active workspace: ${tenantName}`, 'completed');
99
99
  phases.push({ id: 'workspace', title: 'Workspace & Profile', status: 'completed', details: tenantName });
100
- // Step 4: Harness analyst report. When the user passes `--remote`, Moda runs
101
- // the analyst server-side (uploading a safe source snapshot) so no local
102
- // coding agent is required; otherwise the local analyst runs as before.
103
- const remote = isTruthyFlag(flagValue(flags, 'remote'));
104
- log(renderInitStep(4, 7, 'Harness Discovery', remote
105
- ? 'Moda runs the harness analyst server-side and writes cited local artifacts.'
106
- : 'Moda Analyst investigates the harness and writes cited local artifacts.'));
107
- emit('harness', remote
108
- ? 'Running Moda-hosted harness discovery.'
109
- : 'Running Moda Analyst harness discovery.');
110
- const harnessResult = await runHarnessCommand(createCommandContext({
100
+ // Step 4: Harness analyst report. Onboarding runs the analyst SERVER-SIDE by
101
+ // default (Moda uploads a safe source snapshot and runs the agent on its own
102
+ // infra) so the customer needs no local coding agent. Opt out with `--local`
103
+ // (or `--remote=false`) to run a local coding-agent analyst instead. If the
104
+ // remote run fails (service/network), we fall back to the local analyst so
105
+ // onboarding never hard-fails.
106
+ const remote = resolveHarnessRemote(flags);
107
+ const runAnalyze = (useRemote) => runHarnessCommand(createCommandContext({
111
108
  command: 'harness',
112
109
  positional: 'analyze',
113
110
  positionals: ['analyze'],
@@ -122,14 +119,44 @@ export async function runInit(flags, options = {}) {
122
119
  json: json || agent,
123
120
  agent,
124
121
  analyst: stringFlag(flags.analyst) ?? 'auto',
125
- remote,
122
+ // Pass the remote/local decision EXPLICITLY. `commandFlags` drops
123
+ // `remote: false`, so a bare `remote: useRemote` would leave the child
124
+ // with no signal and it would re-derive remoteness from
125
+ // `MODA_HARNESS_REMOTE` — silently overriding `--local` and the
126
+ // remote-failure fallback below. `--no-remote` survives (it's `true`)
127
+ // and takes precedence over the env in `isRemoteHarnessAnalyze`.
128
+ remote: useRemote ? true : undefined,
129
+ 'no-remote': useRemote ? undefined : true,
126
130
  quiet: true,
127
131
  progress: true,
128
132
  'no-telemetry': true,
129
133
  }),
130
134
  }));
135
+ log(renderInitStep(4, 7, 'Harness Discovery', remote
136
+ ? 'Moda runs the harness analyst server-side and writes cited local artifacts.'
137
+ : 'Moda Analyst investigates the harness and writes cited local artifacts.'));
138
+ emit('harness', remote
139
+ ? 'Running Moda-hosted harness discovery.'
140
+ : 'Running Moda Analyst harness discovery.');
141
+ let harnessResult;
142
+ try {
143
+ harnessResult = await runAnalyze(remote);
144
+ }
145
+ catch (error) {
146
+ // A remote failure (service down, upload error) must not sink onboarding —
147
+ // fall back to the local analyst path. If the caller pinned `--remote`
148
+ // explicitly, respect that and surface the failure.
149
+ if (remote && !isTruthyFlag(flagValue(flags, 'remote'))) {
150
+ warn(renderInitNote(`Server-side harness discovery unavailable (${error instanceof Error ? error.message : String(error)}); falling back to local analyst.`, 'warning'));
151
+ emit('harness', 'Server-side discovery failed; falling back to local analyst.', 'warning');
152
+ harnessResult = await runAnalyze(false);
153
+ }
154
+ else {
155
+ throw error;
156
+ }
157
+ }
131
158
  if (harnessResult.exitCode !== 0) {
132
- throw new CliInputError('Moda Analyst produced a harness report that did not pass validation.', 'Review .moda/harness-report-validation.json, then rerun `moda init --analyst=claude|cursor|codex` or `moda harness analyze --analyst=local-scan --summary`.');
159
+ throw new CliInputError('Moda Analyst produced a harness report that did not pass validation.', 'Review .moda/harness-report-validation.json, then rerun `moda init --local --analyst=claude|cursor|codex` or `moda harness analyze --analyst=local-scan --summary`.');
133
160
  }
134
161
  log(renderInitNote('Wrote or verified .moda/harness-report.json, .moda/harness.json, and .moda/HARNESS.md'));
135
162
  if (human)
@@ -628,9 +655,12 @@ async function shouldIntegrateSdk(flags, yes) {
628
655
  function parseOptionalBoolean(value) {
629
656
  if (value === undefined)
630
657
  return undefined;
631
- if (value === true || value === 'true' || value === '1' || value === 'yes')
658
+ // The CLI parser normalizes numeric-looking flag values to numbers, so
659
+ // `--remote=1` arrives as number 1 and `--remote=0` as number 0 (not the
660
+ // string spellings); accept both alongside the string/boolean forms.
661
+ if (value === true || value === 1 || value === 'true' || value === '1' || value === 'yes')
632
662
  return true;
633
- if (value === false || value === 'false' || value === '0' || value === 'no')
663
+ if (value === false || value === 0 || value === 'false' || value === '0' || value === 'no')
634
664
  return false;
635
665
  return undefined;
636
666
  }
@@ -639,6 +669,21 @@ function isTruthyFlag(value) {
639
669
  // number 1), so accept the numeric `1` alongside the string spellings.
640
670
  return value === true || value === 1 || value === 'true' || value === '1' || value === 'yes';
641
671
  }
672
+ /**
673
+ * Onboarding runs harness discovery SERVER-SIDE by default so the customer
674
+ * needs no local coding agent. Opt out with `--local`, `--no-remote`, or
675
+ * `--remote=false` to use a local coding-agent analyst instead.
676
+ */
677
+ export function resolveHarnessRemote(flags) {
678
+ if (isTruthyFlag(flagValue(flags, 'local')))
679
+ return false;
680
+ if (isTruthyFlag(flagValue(flags, 'no-remote', 'no_remote')))
681
+ return false;
682
+ const remote = parseOptionalBoolean(flagValue(flags, 'remote'));
683
+ if (remote !== undefined)
684
+ return remote;
685
+ return true;
686
+ }
642
687
  function flagValue(flags, ...keys) {
643
688
  for (const key of keys) {
644
689
  if (flags[key] !== undefined)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.13.3",
3
+ "version": "1.13.4",
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-06T18:51:44.915Z",
4
- "cli_version": "1.13.3",
3
+ "bundled_at": "2026-07-06T19:06:41.988Z",
4
+ "cli_version": "1.13.4",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-node-anthropic",