@moda-ai/cli 1.35.0 → 1.37.0

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.
@@ -1,16 +1,9 @@
1
1
  import {
2
- SKILL_SOURCE_INTEGRATION_BUNDLED,
3
2
  buildDoctorReport,
4
- claudeSkillToCursorRule,
5
- detectAgents,
6
3
  initPrompts,
7
- installRules,
8
- packageAssetPath,
9
- readSkillsManifest,
10
4
  runPromptSync,
11
- runSkillSync,
12
- upsertSkillManifestRecord
13
- } from "./cli-7qkhy41j.js";
5
+ runSkillSync
6
+ } from "./cli-ert1sbbt.js";
14
7
  import {
15
8
  codingAgentDisplayName,
16
9
  describeCodingAgentEvent,
@@ -18,6 +11,7 @@ import {
18
11
  describeSyncTenantLabel,
19
12
  detectAvailableCodingAgents,
20
13
  isDeterministicLocalAnalyst,
14
+ isRecoverableRemoteRun,
21
15
  pollRemoteAnalyzeUntilDone,
22
16
  renderInitBanner,
23
17
  renderInitComplete,
@@ -28,14 +22,23 @@ import {
28
22
  runHarnessCommand,
29
23
  startRemoteAnalyze,
30
24
  stripAnsi
31
- } from "./cli-0p6n391h.js";
25
+ } from "./cli-07tv75te.js";
26
+ import {
27
+ bundledSkillsDir,
28
+ installIntegrationSkill,
29
+ readBundledIndex
30
+ } from "./cli-c5c6f0yw.js";
31
+ import {
32
+ detectAgents,
33
+ installRules
34
+ } from "./cli-ssfq84k5.js";
32
35
  import {
33
36
  selectTenantAndCreateKey
34
- } from "./cli-scjxgaba.js";
37
+ } from "./cli-qgf2cz2v.js";
35
38
  import {
36
39
  loadToken,
37
40
  login
38
- } from "./cli-yz99t7n8.js";
41
+ } from "./cli-strngwn3.js";
39
42
  import {
40
43
  CliAuthError,
41
44
  CliInputError,
@@ -43,154 +46,47 @@ import {
43
46
  createCommandContext,
44
47
  resolveIngestUrl,
45
48
  resolveModaBaseUrl
46
- } from "./cli-drabjmaj.js";
49
+ } from "./cli-q79sq80a.js";
50
+ import"./cli-0v6na3yp.js";
47
51
 
48
52
  // src/init/index.ts
49
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readdirSync as readdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "node:fs";
50
- import { dirname as dirname4, join as join6 } from "node:path";
53
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
54
+ import { dirname as dirname3, join as join5 } from "node:path";
51
55
  import { checkbox, confirm, select } from "@inquirer/prompts";
52
56
 
53
57
  // src/init/sdk-guide.ts
54
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
55
- import { dirname as dirname2, join as join3 } from "node:path";
56
-
57
- // src/skills-bundled.ts
58
- import { createHash } from "node:crypto";
59
- import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
60
- import { dirname, join, relative } from "node:path";
61
- var BUNDLED_SKILLS_DIR = packageAssetPath("skills", "integration");
62
- function bundledSkillsDir() {
63
- return process.env.MODA_BUNDLED_SKILLS_DIR || BUNDLED_SKILLS_DIR;
64
- }
65
- function readBundledIndex() {
66
- const indexPath = join(bundledSkillsDir(), "index.json");
67
- if (!existsSync(indexPath))
68
- return null;
69
- try {
70
- const parsed = JSON.parse(readFileSync(indexPath, "utf8"));
71
- if (!Array.isArray(parsed.skills))
72
- return null;
73
- return parsed;
74
- } catch {
75
- return null;
76
- }
77
- }
78
- function installIntegrationSkill(skillId, cwd = process.cwd()) {
79
- if (!/^[a-z0-9-]+$/.test(skillId)) {
80
- throw new Error(`Invalid integration skill id: ${JSON.stringify(skillId).slice(0, 80)}`);
81
- }
82
- const sourceDir = join(bundledSkillsDir(), skillId);
83
- if (!existsSync(join(sourceDir, "SKILL.md"))) {
84
- const bundled = readBundledIndex()?.skills.map((skill) => skill.id) ?? [];
85
- throw new Error(`Integration skill '${skillId}' is not bundled with this CLI.${bundled.length > 0 ? ` Bundled skills: ${bundled.join(", ")}` : ""}`);
86
- }
87
- const files = listFilesRecursive(sourceDir).sort();
88
- const plannedWrites = [];
89
- for (const file of files) {
90
- const relPath = relative(sourceDir, file).replace(/\\/g, "/");
91
- plannedWrites.push({
92
- targetRelPath: `.claude/skills/${skillId}/${relPath}`,
93
- targetPath: join(cwd, ".claude", "skills", skillId, relPath),
94
- content: readFileSync(file, "utf8")
95
- });
96
- }
97
- const writeCursor = detectAgents().includes("Cursor") || existsSync(join(cwd, ".cursor"));
98
- if (writeCursor) {
99
- const skillMd = readFileSync(join(sourceDir, "SKILL.md"), "utf8");
100
- plannedWrites.push({
101
- targetRelPath: `.cursor/rules/${skillId}.mdc`,
102
- targetPath: join(cwd, ".cursor", "rules", `${skillId}.mdc`),
103
- content: claudeSkillToCursorRule(skillMd)
104
- });
105
- }
106
- const ownedPaths = new Set((readSkillsManifest(cwd)?.records ?? []).flatMap((record) => record.paths));
107
- const collisions = plannedWrites.filter((write) => existsSync(write.targetPath) && !ownedPaths.has(write.targetRelPath));
108
- if (collisions.length > 0) {
109
- throw new Error(`Refusing to overwrite ${collisions.length} existing file(s) not managed by Moda: ` + `${collisions.map((write) => write.targetRelPath).join(", ")}. ` + "Move or remove them, then re-run to install the bundled integration skill.");
110
- }
111
- const paths = [];
112
- let wroteAny = false;
113
- for (const write of plannedWrites) {
114
- if (writeIfChanged(write.targetPath, write.content))
115
- wroteAny = true;
116
- paths.push(write.targetRelPath);
117
- }
118
- upsertSkillManifestRecord(cwd, {
119
- skillId,
120
- contentHash: hashSkillDir(sourceDir, files),
121
- source: SKILL_SOURCE_INTEGRATION_BUNDLED,
122
- paths
123
- });
124
- return { skillId, status: wroteAny ? "installed" : "unchanged", paths };
125
- }
126
- function writeIfChanged(path, content) {
127
- if (existsSync(path)) {
128
- try {
129
- if (readFileSync(path, "utf8") === content)
130
- return false;
131
- } catch {}
132
- }
133
- mkdirSync(dirname(path), { recursive: true });
134
- writeFileSync(path, content);
135
- return true;
136
- }
137
- function listFilesRecursive(dir) {
138
- const files = [];
139
- const stack = [dir];
140
- while (stack.length > 0) {
141
- const current = stack.pop();
142
- if (!current)
143
- continue;
144
- for (const entry of readdirSync(current, { withFileTypes: true })) {
145
- const path = join(current, entry.name);
146
- if (entry.isDirectory())
147
- stack.push(path);
148
- else if (entry.isFile())
149
- files.push(path);
150
- }
151
- }
152
- return files;
153
- }
154
- function hashSkillDir(sourceDir, files) {
155
- const hash = createHash("sha256");
156
- for (const file of files) {
157
- hash.update(relative(sourceDir, file).replace(/\\/g, "/"));
158
- hash.update("\x00");
159
- hash.update(readFileSync(file));
160
- hash.update("\x00");
161
- }
162
- return hash.digest("hex");
163
- }
58
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
59
+ import { dirname, join as join2 } from "node:path";
164
60
 
165
61
  // src/init/sdk-catalog.ts
166
62
  import {
167
63
  copyFileSync,
168
- existsSync as existsSync2,
169
- mkdirSync as mkdirSync2,
170
- readdirSync as readdirSync2,
64
+ existsSync,
65
+ mkdirSync,
66
+ readdirSync,
171
67
  rmSync,
172
68
  statSync
173
69
  } from "node:fs";
174
- import { join as join2, relative as relative2, resolve } from "node:path";
70
+ import { join, relative, resolve } from "node:path";
175
71
  function materializeSdkCatalog(cwd, runId) {
176
72
  if (!/^[a-z0-9-]+$/.test(runId)) {
177
73
  throw new Error(`Invalid SDK catalog run id: ${JSON.stringify(runId).slice(0, 80)}`);
178
74
  }
179
75
  const sourceDir = bundledSkillsDir();
180
- const indexPath = join2(sourceDir, "index.json");
181
- if (!existsSync2(sourceDir) || !existsSync2(indexPath)) {
76
+ const indexPath = join(sourceDir, "index.json");
77
+ if (!existsSync(sourceDir) || !existsSync(indexPath)) {
182
78
  throw missingBundleError(sourceDir);
183
79
  }
184
80
  const index = readBundledIndex();
185
81
  if (!index) {
186
82
  throw missingBundleError(sourceDir);
187
83
  }
188
- const dir = join2(cwd, ".moda", "tmp", `sdk-catalog-${runId}`);
84
+ const dir = join(cwd, ".moda", "tmp", `sdk-catalog-${runId}`);
189
85
  cleanupSdkCatalog(dir);
190
86
  copyDirectoryRecursive(sourceDir, dir);
191
87
  return {
192
88
  dir,
193
- relativeDir: relative2(cwd, dir).replace(/\\/g, "/"),
89
+ relativeDir: relative(cwd, dir).replace(/\\/g, "/"),
194
90
  index
195
91
  };
196
92
  }
@@ -216,10 +112,10 @@ function renderCatalogListing(index) {
216
112
  `);
217
113
  }
218
114
  function copyDirectoryRecursive(sourceDir, targetDir) {
219
- mkdirSync2(targetDir, { recursive: true });
220
- for (const entry of readdirSync2(sourceDir, { withFileTypes: true })) {
221
- const source = join2(sourceDir, entry.name);
222
- const target = join2(targetDir, entry.name);
115
+ mkdirSync(targetDir, { recursive: true });
116
+ for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
117
+ const source = join(sourceDir, entry.name);
118
+ const target = join(targetDir, entry.name);
223
119
  if (entry.isDirectory()) {
224
120
  copyDirectoryRecursive(source, target);
225
121
  } else if (entry.isFile()) {
@@ -230,9 +126,9 @@ function copyDirectoryRecursive(sourceDir, targetDir) {
230
126
  function missingBundleError(sourceDir) {
231
127
  let detail = "is absent";
232
128
  try {
233
- if (existsSync2(sourceDir) && !statSync(sourceDir).isDirectory()) {
129
+ if (existsSync(sourceDir) && !statSync(sourceDir).isDirectory()) {
234
130
  detail = "is not a directory";
235
- } else if (existsSync2(sourceDir)) {
131
+ } else if (existsSync(sourceDir)) {
236
132
  detail = "has no readable index.json";
237
133
  }
238
134
  } catch {
@@ -245,10 +141,10 @@ function missingBundleError(sourceDir) {
245
141
  var SDK_GUIDE_PATH = ".moda/SDK_INTEGRATION.md";
246
142
  function writeSdkIntegrationGuide(cwd = process.cwd()) {
247
143
  const relativePath = SDK_GUIDE_PATH;
248
- const absolutePath = join3(cwd, relativePath);
144
+ const absolutePath = join2(cwd, relativePath);
249
145
  const content = renderSdkIntegrationGuideContent();
250
- mkdirSync3(dirname2(absolutePath), { recursive: true });
251
- writeFileSync2(absolutePath, content);
146
+ mkdirSync2(dirname(absolutePath), { recursive: true });
147
+ writeFileSync(absolutePath, content);
252
148
  return relativePath;
253
149
  }
254
150
  function renderSdkIntegrationGuideContent() {
@@ -367,11 +263,11 @@ function readRuntimeSummary(cwd) {
367
263
  providers: [],
368
264
  runtimeAgentNames: []
369
265
  };
370
- const path = join3(cwd, ".moda", "harness.json");
371
- if (!existsSync3(path))
266
+ const path = join2(cwd, ".moda", "harness.json");
267
+ if (!existsSync2(path))
372
268
  return empty;
373
269
  try {
374
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
270
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
375
271
  if (!isRecord(parsed))
376
272
  return empty;
377
273
  const runtimeAgents = Array.isArray(parsed.runtimeAgents) ? parsed.runtimeAgents : [];
@@ -415,22 +311,22 @@ function isRecord(value) {
415
311
  import { randomBytes as randomBytes2 } from "node:crypto";
416
312
  import {
417
313
  closeSync,
418
- existsSync as existsSync5,
419
- mkdirSync as mkdirSync4,
314
+ existsSync as existsSync4,
315
+ mkdirSync as mkdirSync3,
420
316
  openSync,
421
- readFileSync as readFileSync4,
317
+ readFileSync as readFileSync3,
422
318
  readSync,
423
- readdirSync as readdirSync3,
319
+ readdirSync as readdirSync2,
424
320
  rmSync as rmSync2,
425
321
  statSync as statSync2,
426
- writeFileSync as writeFileSync3
322
+ writeFileSync as writeFileSync2
427
323
  } from "node:fs";
428
- import { dirname as dirname3, join as join5, relative as relative3, resolve as resolve2 } from "node:path";
324
+ import { dirname as dirname2, join as join4, relative as relative2, resolve as resolve2 } from "node:path";
429
325
  import { spawnSync } from "node:child_process";
430
326
 
431
327
  // src/init/sdk-protocol.ts
432
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
433
- import { join as join4 } from "node:path";
328
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
329
+ import { join as join3 } from "node:path";
434
330
  import { z } from "zod";
435
331
  var SDK_RESULT_SCHEMA_VERSION = "moda.sdk_integration_result.v1";
436
332
  var SDK_RESULT_JSON_BEGIN = "MODA_SDK_RESULT_JSON_BEGIN";
@@ -522,10 +418,10 @@ var SdkAgentResultSchema = z.object({
522
418
  });
523
419
  function extractSdkAgentResult(opts) {
524
420
  const diagnostics = [];
525
- const resultPath = join4(opts.cwd, SDK_RESULT_FILE);
526
- if (existsSync4(resultPath)) {
421
+ const resultPath = join3(opts.cwd, SDK_RESULT_FILE);
422
+ if (existsSync3(resultPath)) {
527
423
  try {
528
- const parsed = parseCandidate(readFileSync3(resultPath, "utf8"));
424
+ const parsed = parseCandidate(readFileSync2(resultPath, "utf8"));
529
425
  if (parsed.result)
530
426
  return { result: parsed.result, source: "file" };
531
427
  diagnostics.push(`result file: ${parsed.detail}`);
@@ -585,6 +481,12 @@ var MAX_TIMEOUT_MS = 600000;
585
481
  function generateVerificationId() {
586
482
  return `moda-verify-${randomBytes(16).toString("hex")}`;
587
483
  }
484
+ function isModaVerificationId(value) {
485
+ return /^moda-verify-[0-9a-f]{32}$/.test(value);
486
+ }
487
+ function isAcceptedVerificationId(value) {
488
+ return isModaVerificationId(value) || /^onb_[A-Za-z0-9]{16}$/.test(value);
489
+ }
588
490
  async function verifyExactTrace(opts) {
589
491
  const now = opts.now ?? Date.now;
590
492
  const sleep = opts.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
@@ -776,7 +678,7 @@ async function runSdkIntegration(options = {}) {
776
678
  const verify = options.verify ?? verifyExactTrace;
777
679
  const maxRepairAttempts = resolveMaxRepairAttempts(options.maxRepairAttempts);
778
680
  const runId = randomBytes2(4).toString("hex");
779
- const verificationId = generateVerificationId();
681
+ const verificationId = options.verificationId ?? generateVerificationId();
780
682
  let catalog;
781
683
  let gitBaseline = null;
782
684
  let runStartedAt = Date.now();
@@ -1162,7 +1064,7 @@ function scanForSecrets(cwd, paths, apiKey) {
1162
1064
  const absPath = resolve2(cwd, relPath);
1163
1065
  if (!absPath.startsWith(resolvedCwd + "/") && absPath !== resolvedCwd)
1164
1066
  continue;
1165
- if (!existsSync5(absPath))
1067
+ if (!existsSync4(absPath))
1166
1068
  continue;
1167
1069
  let stat;
1168
1070
  try {
@@ -1174,7 +1076,7 @@ function scanForSecrets(cwd, paths, apiKey) {
1174
1076
  continue;
1175
1077
  let content;
1176
1078
  try {
1177
- content = stat.size > MAX_SCAN_FILE_BYTES ? readFilePrefix(absPath, MAX_SCAN_FILE_BYTES) : readFileSync4(absPath, "utf8");
1079
+ content = stat.size > MAX_SCAN_FILE_BYTES ? readFilePrefix(absPath, MAX_SCAN_FILE_BYTES) : readFileSync3(absPath, "utf8");
1178
1080
  } catch {
1179
1081
  continue;
1180
1082
  }
@@ -1210,7 +1112,7 @@ function collectSecretScanFiles(cwd, paths) {
1210
1112
  const addFile = (path) => {
1211
1113
  if (files.length >= MAX_SCANNED_FILES || !isInsideRepo(path))
1212
1114
  return;
1213
- const relPath = relative3(resolvedCwd, path).replace(/\\/g, "/");
1115
+ const relPath = relative2(resolvedCwd, path).replace(/\\/g, "/");
1214
1116
  if (!relPath || seenFiles.has(relPath))
1215
1117
  return;
1216
1118
  seenFiles.add(relPath);
@@ -1221,13 +1123,13 @@ function collectSecretScanFiles(cwd, paths) {
1221
1123
  return;
1222
1124
  let entries;
1223
1125
  try {
1224
- entries = readdirSync3(dir, { withFileTypes: true });
1126
+ entries = readdirSync2(dir, { withFileTypes: true });
1225
1127
  } catch {
1226
1128
  return;
1227
1129
  }
1228
1130
  for (const entry of entries) {
1229
1131
  if (entry.isFile() && (entry.name === ".env" || entry.name.startsWith(".env."))) {
1230
- addFile(join5(dir, entry.name));
1132
+ addFile(join4(dir, entry.name));
1231
1133
  }
1232
1134
  }
1233
1135
  };
@@ -1240,14 +1142,14 @@ function collectSecretScanFiles(cwd, paths) {
1240
1142
  visitedDirectories += 1;
1241
1143
  let entries;
1242
1144
  try {
1243
- entries = readdirSync3(dir, { withFileTypes: true });
1145
+ entries = readdirSync2(dir, { withFileTypes: true });
1244
1146
  } catch {
1245
1147
  continue;
1246
1148
  }
1247
1149
  for (const entry of entries) {
1248
1150
  if (files.length >= MAX_SCANNED_FILES)
1249
1151
  break;
1250
- const path = join5(dir, entry.name);
1152
+ const path = join4(dir, entry.name);
1251
1153
  if (entry.isDirectory())
1252
1154
  stack.push(path);
1253
1155
  else if (entry.isFile())
@@ -1263,8 +1165,8 @@ function collectSecretScanFiles(cwd, paths) {
1263
1165
  const absPath = resolve2(cwd, relPath);
1264
1166
  if (!isInsideRepo(absPath))
1265
1167
  continue;
1266
- addEnvFiles(dirname3(absPath));
1267
- if (!existsSync5(absPath))
1168
+ addEnvFiles(dirname2(absPath));
1169
+ if (!existsSync4(absPath))
1268
1170
  continue;
1269
1171
  let stat;
1270
1172
  try {
@@ -1353,9 +1255,9 @@ function renderProtocolRetryPrompt(catalogDir) {
1353
1255
  `);
1354
1256
  }
1355
1257
  function removeAgentResultFile(cwd) {
1356
- const path = join5(cwd, SDK_RESULT_FILE);
1258
+ const path = join4(cwd, SDK_RESULT_FILE);
1357
1259
  try {
1358
- if (existsSync5(path))
1260
+ if (existsSync4(path))
1359
1261
  rmSync2(path);
1360
1262
  } catch {}
1361
1263
  }
@@ -1469,9 +1371,9 @@ function composeFinalDetail(status, detail, verification, apiKey, guardsDegraded
1469
1371
  return combined ? redactSecretText(combined, apiKey) : undefined;
1470
1372
  }
1471
1373
  function writeFinalOutcomeArtifact(cwd, payload) {
1472
- const path = join5(cwd, SDK_RESULT_FILE);
1473
- mkdirSync4(dirname3(path), { recursive: true });
1474
- writeFileSync3(path, `${JSON.stringify({
1374
+ const path = join4(cwd, SDK_RESULT_FILE);
1375
+ mkdirSync3(dirname2(path), { recursive: true });
1376
+ writeFileSync2(path, `${JSON.stringify({
1475
1377
  schema_version: OUTCOME_SCHEMA_VERSION,
1476
1378
  status: payload.status,
1477
1379
  verification_id: payload.verification_id,
@@ -1681,13 +1583,38 @@ function visibleLength(value) {
1681
1583
 
1682
1584
  // src/init/index.ts
1683
1585
  var HARNESS_PULL_RECOVERY = "moda harness pull --sync";
1586
+ var HARNESS_SKIP_NEXT = {
1587
+ unavailable: "retry",
1588
+ lost: "pull",
1589
+ unfetched: "pull",
1590
+ failed: "retry"
1591
+ };
1592
+ var HARNESS_SKIP_DETAILS = {
1593
+ unavailable: "hosted analysis unavailable",
1594
+ lost: "lost track of the hosted run",
1595
+ unfetched: "could not fetch the finished run",
1596
+ failed: "hosted run failed"
1597
+ };
1598
+ var HARNESS_SKIP_PLAN = {
1599
+ unavailable: "unavailable",
1600
+ lost: "status unknown",
1601
+ unfetched: "finished but not fetched",
1602
+ failed: "failed"
1603
+ };
1604
+ var HARNESS_SKIP_EVENT = {
1605
+ unavailable: "unavailable",
1606
+ lost: "could not be followed",
1607
+ unfetched: "finished but could not be fetched",
1608
+ failed: "failed on Moda"
1609
+ };
1684
1610
  var LANE_EVENT_THROTTLE_MS = 2000;
1685
1611
  async function runInit(flags, options = {}) {
1686
1612
  const explicitYes = flags.yes === true || flags.yes === "true" || flags.y === true || flags.y === "true";
1687
1613
  const json = flags.json === true || flags.json === "true";
1688
1614
  const agent = flags.agent === true || flags.agent === "true";
1689
1615
  const human = !json && !agent;
1690
- const yes = explicitYes || json || agent;
1616
+ const noPrompts = flags.non_interactive === true || flags.non_interactive === "true";
1617
+ const yes = explicitYes || json || agent || noPrompts;
1691
1618
  const phases = [];
1692
1619
  const log = (message) => {
1693
1620
  if (human)
@@ -1717,7 +1644,7 @@ async function runInit(flags, options = {}) {
1717
1644
  log(renderInitStep(1, 5, "Sign in", "No environment variable files are written. File changes are listed before completion."));
1718
1645
  let token = loadToken({ profile });
1719
1646
  if (!token) {
1720
- if (json || agent) {
1647
+ if (json || agent || noPrompts) {
1721
1648
  throw new CliAuthError("Browser authentication is required before non-interactive init can continue.", {
1722
1649
  reason: "no_stored_credentials",
1723
1650
  login_command: ["moda", "auth", "login"],
@@ -1746,11 +1673,15 @@ async function runInit(flags, options = {}) {
1746
1673
  log(renderInitStep(2, 5, "Workspace"));
1747
1674
  emit("workspace", "Selecting workspace and creating API key.");
1748
1675
  const tenantIdFlag = stringFlag(flagValue(flags, "tenant-id", "tenant_id"));
1676
+ const verificationId = stringFlag(flagValue(flags, "verification-id", "verification_id"));
1677
+ if (verificationId !== undefined && !isAcceptedVerificationId(verificationId)) {
1678
+ throw new CliInputError(`Invalid --verification-id ${JSON.stringify(verificationId).slice(0, 80)}`, "Pass the onb_ id shown in the Moda onboarding page, or omit the flag.");
1679
+ }
1749
1680
  const tenantIdEnv = stringFlag(process.env.MODA_TENANT_ID);
1750
1681
  const { apiKey, tenantId: selectedTenantId, tenantName } = await selectTenantAndCreateKey({
1751
1682
  tenantId: tenantIdFlag ?? tenantIdEnv,
1752
1683
  tenantIdSource: tenantIdFlag ? "flag" : tenantIdEnv ? "env" : undefined,
1753
- nonInteractive: json || agent,
1684
+ nonInteractive: json || agent || noPrompts,
1754
1685
  silent: !human,
1755
1686
  resumeCommand: ["moda", "init", ...resumeFlagsForInit(flags)],
1756
1687
  profile
@@ -1854,6 +1785,8 @@ async function runInit(flags, options = {}) {
1854
1785
  log(renderInitStep(4, 5, "Set up", remote ? "Harness analysis runs on Moda while local setup continues. No further input is needed." : "Runs unattended. No further input is needed."));
1855
1786
  const rootDir = process.cwd();
1856
1787
  const allowLocalFallback = !isTruthyFlag(flagValue(flags, "remote"));
1788
+ const launchLocalAnalyst = human && !noPrompts || stringFlag(flags.analyst) !== undefined;
1789
+ const HARNESS_RETRY = "moda harness analyze --remote --summary --experimental";
1857
1790
  const nestedHarnessFlags = (extra) => commandFlags({
1858
1791
  summary: true,
1859
1792
  yes: true,
@@ -1886,6 +1819,23 @@ async function runInit(flags, options = {}) {
1886
1819
  const validationFailure = () => 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`.");
1887
1820
  let harnessPhase = { id: "harness", title: "Harness discovery", status: "completed" };
1888
1821
  let harnessDeferred = false;
1822
+ let harnessSkipped = false;
1823
+ let harnessSkip = "unavailable";
1824
+ const skipHarness = (error, kind = "unavailable") => {
1825
+ harnessSkipped = true;
1826
+ harnessDeferred = true;
1827
+ harnessSkip = kind;
1828
+ const reason = error instanceof Error ? error.message : String(error);
1829
+ const next = HARNESS_SKIP_NEXT[kind] === "pull" ? HARNESS_PULL_RECOVERY : HARNESS_RETRY;
1830
+ harnessPhase = {
1831
+ id: "harness",
1832
+ title: "Harness discovery",
1833
+ status: "warning",
1834
+ details: `skipped: ${HARNESS_SKIP_DETAILS[kind]}`
1835
+ };
1836
+ markApplied(setupPlan, "harness", `Hosted analysis ${HARNESS_SKIP_PLAN[kind]}; run \`${next}\` later.`);
1837
+ emit("harness", `Hosted harness discovery ${HARNESS_SKIP_EVENT[kind]} (${reason}); skipped. Continue with \`${next}\`.`, "warning");
1838
+ };
1889
1839
  const approveAndSyncHarness = async () => {
1890
1840
  const graphSignals = readHarnessQualitySignals(rootDir);
1891
1841
  const repoSignals = detectRepoLlmSignals(rootDir);
@@ -1896,7 +1846,7 @@ async function runInit(flags, options = {}) {
1896
1846
  } : undefined;
1897
1847
  if (signals && harnessLooksIncomplete(signals)) {
1898
1848
  const names = [...signals.frameworks, ...signals.providers].join(", ");
1899
- const retryCommand = remote ? "moda harness analyze --remote --summary" : "moda harness analyze --analyst=claude|cursor|codex --summary";
1849
+ const retryCommand = remote ? "moda harness analyze --remote --summary --experimental" : "moda harness analyze --analyst=claude|cursor|codex --summary";
1900
1850
  warn(renderInitNote(`Harness discovery found 0 runtime agents, but this repo has LLM framework signals (${names}). ` + "Discovery likely failed, so the result was NOT approved or synced.", "warning"));
1901
1851
  warn(` Retry with \`${retryCommand}\`, then \`moda harness approve --yes && moda harness sync --from-report\`.`);
1902
1852
  emit("harness", "Harness discovery found 0 agents despite LLM framework signals; sync withheld.", "warning");
@@ -2003,12 +1953,16 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2003
1953
  } catch (error) {
2004
1954
  if (!allowLocalFallback)
2005
1955
  throw error;
2006
- warn(renderInitNote(`Server-side harness discovery unavailable (${error instanceof Error ? error.message : String(error)}); falling back to local analyst.`, "warning"));
2007
- emit("harness", "Server-side discovery failed; falling back to local analyst.", "warning");
2008
1956
  remote = false;
1957
+ if (launchLocalAnalyst) {
1958
+ warn(renderInitNote(`Server-side harness discovery unavailable (${error instanceof Error ? error.message : String(error)}); falling back to local analyst.`, "warning"));
1959
+ emit("harness", "Server-side discovery failed; falling back to local analyst.", "warning");
1960
+ } else {
1961
+ skipHarness(error);
1962
+ }
2009
1963
  }
2010
1964
  }
2011
- if (!remote) {
1965
+ if (!remote && !harnessSkipped) {
2012
1966
  await finishLocalAnalyze();
2013
1967
  }
2014
1968
  emit("apply", "Applying approved setup changes.");
@@ -2068,6 +2022,7 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2068
2022
  const verificationBaseUrl = resolveModaBaseUrl({ profile });
2069
2023
  sdkIntegration = await runSdkIntegration({
2070
2024
  apiKey,
2025
+ verificationId,
2071
2026
  ingestUrl: resolveIngestUrl({ profile }),
2072
2027
  fetchAudit: (id) => fetchAuditWithApiKey(id, apiKey, verificationBaseUrl),
2073
2028
  onProgress: (message) => {
@@ -2093,17 +2048,21 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2093
2048
  if (remote && pollPromise && remoteRunId) {
2094
2049
  const final = await pollPromise;
2095
2050
  process.removeListener("SIGINT", onRemoteSigint);
2096
- const fallBackToLocal = async (error) => {
2051
+ const fallBackToLocal = async (error, kind) => {
2097
2052
  board?.fail("harness", "hosted analysis failed");
2098
2053
  board?.stop();
2099
2054
  if (!allowLocalFallback)
2100
2055
  throw error;
2056
+ if (!launchLocalAnalyst) {
2057
+ skipHarness(error, kind);
2058
+ return;
2059
+ }
2101
2060
  warn(renderInitNote(`Server-side harness discovery failed (${error instanceof Error ? error.message : String(error)}); falling back to local analyst.`, "warning"));
2102
2061
  emit("harness", "Server-side discovery failed; falling back to local analyst.", "warning");
2103
2062
  await finishLocalAnalyze();
2104
2063
  };
2105
2064
  if (pollError !== undefined) {
2106
- await fallBackToLocal(pollError);
2065
+ await fallBackToLocal(pollError, "lost");
2107
2066
  } else if (!final || final.status !== "completed" && final.status !== "failed") {
2108
2067
  harnessDeferred = true;
2109
2068
  harnessPhase = { id: "harness", title: "Harness discovery", status: "warning", details: "still running on Moda" };
@@ -2123,7 +2082,7 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2123
2082
  flags: nestedHarnessFlags({ "run-id": remoteRunId })
2124
2083
  }));
2125
2084
  } catch (error) {
2126
- await fallBackToLocal(error);
2085
+ await fallBackToLocal(error, isRecoverableRemoteRun(final) ? "unfetched" : "failed");
2127
2086
  }
2128
2087
  if (pullResult) {
2129
2088
  if (pullResult.exitCode !== 0) {
@@ -2175,7 +2134,7 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2175
2134
  log(renderInitComplete({
2176
2135
  tenantName,
2177
2136
  dashboardUrl: `${resolveModaBaseUrl({ profile })}/dashboard`,
2178
- harness: harnessDeferred ? { state: "pending" } : { state: "mapped", ...readHarnessGraphCounts() },
2137
+ harness: harnessSkipped ? { state: "skipped", reason: harnessSkip, next: HARNESS_SKIP_NEXT[harnessSkip] === "pull" ? HARNESS_PULL_RECOVERY : HARNESS_RETRY } : harnessDeferred ? { state: "pending" } : { state: "mapped", ...readHarnessGraphCounts() },
2179
2138
  filesChanged: changedFiles(setupPlan),
2180
2139
  skipped: setupPlan.filter((item) => item.status === "skipped" || item.status === "manual"),
2181
2140
  telemetrySeen
@@ -2359,9 +2318,9 @@ var GENERATED_WORKFLOW_CLI = `@moda-ai/cli@${VERSION}`;
2359
2318
  var GENERATED_WORKFLOW_NPX = `npx -y --package=${GENERATED_WORKFLOW_CLI} moda`;
2360
2319
  function writeCiCdWorkflow(cwd = process.cwd()) {
2361
2320
  const relativePath = ".github/workflows/moda-validation.yml";
2362
- const path = join6(cwd, relativePath);
2363
- mkdirSync5(dirname4(path), { recursive: true });
2364
- writeFileSync4(path, `${[
2321
+ const path = join5(cwd, relativePath);
2322
+ mkdirSync4(dirname3(path), { recursive: true });
2323
+ writeFileSync3(path, `${[
2365
2324
  "name: Moda Validation",
2366
2325
  "",
2367
2326
  "on:",
@@ -2394,9 +2353,9 @@ function writeCiCdWorkflow(cwd = process.cwd()) {
2394
2353
  }
2395
2354
  function writeAutosyncWorkflow(cwd = process.cwd()) {
2396
2355
  const relativePath = ".github/workflows/moda-autosync.yml";
2397
- const path = join6(cwd, relativePath);
2398
- mkdirSync5(dirname4(path), { recursive: true });
2399
- writeFileSync4(path, `${[
2356
+ const path = join5(cwd, relativePath);
2357
+ mkdirSync4(dirname3(path), { recursive: true });
2358
+ writeFileSync3(path, `${[
2400
2359
  "name: Moda Autosync",
2401
2360
  "",
2402
2361
  "on:",
@@ -2435,8 +2394,8 @@ function writeAutosyncWorkflow(cwd = process.cwd()) {
2435
2394
  }
2436
2395
  function writeHarnessRescanWorkflow(cwd = process.cwd(), paths) {
2437
2396
  const relativePath = ".github/workflows/moda-harness-rescan.yml";
2438
- const path = join6(cwd, relativePath);
2439
- mkdirSync5(dirname4(path), { recursive: true });
2397
+ const path = join5(cwd, relativePath);
2398
+ mkdirSync4(dirname3(path), { recursive: true });
2440
2399
  const pathScope = paths !== undefined && paths.length > 0 ? [
2441
2400
  " # Only pushes touching these paths trigger a rescan.",
2442
2401
  " paths:",
@@ -2446,7 +2405,7 @@ function writeHarnessRescanWorkflow(cwd = process.cwd(), paths) {
2446
2405
  " # paths:",
2447
2406
  " # - 'src/agents/**'"
2448
2407
  ];
2449
- writeFileSync4(path, `${[
2408
+ writeFileSync3(path, `${[
2450
2409
  "name: Moda Harness Rescan",
2451
2410
  "",
2452
2411
  "on:",
@@ -2492,11 +2451,11 @@ function writeHarnessRescanWorkflow(cwd = process.cwd(), paths) {
2492
2451
  return relativePath;
2493
2452
  }
2494
2453
  function printHarnessDetectionSummary(cwd = process.cwd()) {
2495
- const manifestPath = join6(cwd, ".moda", "harness.json");
2496
- if (!existsSync6(manifestPath))
2454
+ const manifestPath = join5(cwd, ".moda", "harness.json");
2455
+ if (!existsSync5(manifestPath))
2497
2456
  return;
2498
2457
  try {
2499
- const graph = JSON.parse(readFileSync5(manifestPath, "utf8"));
2458
+ const graph = JSON.parse(readFileSync4(manifestPath, "utf8"));
2500
2459
  const frameworkNames = (graph.agentFrameworks ?? []).map((framework) => typeof framework.name === "string" ? framework.name : undefined).filter((name) => Boolean(name));
2501
2460
  const providerNames = (graph.modelProviders ?? []).map((provider) => typeof provider.name === "string" ? provider.name : undefined).filter((name) => Boolean(name));
2502
2461
  console.log(` Detected harness: ${(graph.runtimeAgents ?? []).length} runtime agent(s), ` + `${(graph.artifacts ?? []).length} artifact(s), ${(graph.relationships ?? []).length} relationship(s)`);
@@ -2517,20 +2476,20 @@ function signalsFromGraph(graph) {
2517
2476
  };
2518
2477
  }
2519
2478
  function readHarnessQualitySignals(cwd = process.cwd()) {
2520
- const reportPath = join6(cwd, ".moda", "harness-report.json");
2521
- if (existsSync6(reportPath)) {
2479
+ const reportPath = join5(cwd, ".moda", "harness-report.json");
2480
+ if (existsSync5(reportPath)) {
2522
2481
  try {
2523
- const report = JSON.parse(readFileSync5(reportPath, "utf8"));
2482
+ const report = JSON.parse(readFileSync4(reportPath, "utf8"));
2524
2483
  if (report.graph && typeof report.graph === "object") {
2525
2484
  return signalsFromGraph(report.graph);
2526
2485
  }
2527
2486
  } catch {}
2528
2487
  }
2529
- const manifestPath = join6(cwd, ".moda", "harness.json");
2530
- if (!existsSync6(manifestPath))
2488
+ const manifestPath = join5(cwd, ".moda", "harness.json");
2489
+ if (!existsSync5(manifestPath))
2531
2490
  return;
2532
2491
  try {
2533
- return signalsFromGraph(JSON.parse(readFileSync5(manifestPath, "utf8")));
2492
+ return signalsFromGraph(JSON.parse(readFileSync4(manifestPath, "utf8")));
2534
2493
  } catch {
2535
2494
  return;
2536
2495
  }
@@ -2648,7 +2607,7 @@ function detectRepoLlmSignals(cwd = process.cwd()) {
2648
2607
  };
2649
2608
  const scanDir = (dir) => {
2650
2609
  try {
2651
- const pkg = JSON.parse(readFileSync5(join6(dir, "package.json"), "utf8"));
2610
+ const pkg = JSON.parse(readFileSync4(join5(dir, "package.json"), "utf8"));
2652
2611
  for (const dep of [
2653
2612
  ...Object.keys(pkg.dependencies ?? {}),
2654
2613
  ...Object.keys(pkg.devDependencies ?? {}),
@@ -2659,7 +2618,7 @@ function detectRepoLlmSignals(cwd = process.cwd()) {
2659
2618
  } catch {}
2660
2619
  for (const file of ["requirements.txt", "pyproject.toml"]) {
2661
2620
  try {
2662
- const text = readFileSync5(join6(dir, file), "utf8");
2621
+ const text = readFileSync4(join5(dir, file), "utf8");
2663
2622
  for (const name of pythonRequirementNames(text, file)) {
2664
2623
  record(pyDependencySignal(name));
2665
2624
  }
@@ -2674,7 +2633,7 @@ function detectRepoLlmSignals(cwd = process.cwd()) {
2674
2633
  scanDir(dir);
2675
2634
  let entries;
2676
2635
  try {
2677
- entries = readdirSync4(dir, { withFileTypes: true });
2636
+ entries = readdirSync3(dir, { withFileTypes: true });
2678
2637
  } catch {
2679
2638
  return;
2680
2639
  }
@@ -2683,18 +2642,18 @@ function detectRepoLlmSignals(cwd = process.cwd()) {
2683
2642
  continue;
2684
2643
  if (REPO_SIGNAL_SKIPPED_DIRS.has(entry.name))
2685
2644
  continue;
2686
- walk(join6(dir, entry.name), depth + 1);
2645
+ walk(join5(dir, entry.name), depth + 1);
2687
2646
  }
2688
2647
  };
2689
2648
  walk(cwd, 0);
2690
2649
  return { frameworks: [...frameworks].sort(), providers: [...providers].sort() };
2691
2650
  }
2692
2651
  function readHarnessGraphCounts(cwd = process.cwd()) {
2693
- const manifestPath = join6(cwd, ".moda", "harness.json");
2694
- if (!existsSync6(manifestPath))
2652
+ const manifestPath = join5(cwd, ".moda", "harness.json");
2653
+ if (!existsSync5(manifestPath))
2695
2654
  return {};
2696
2655
  try {
2697
- const graph = JSON.parse(readFileSync5(manifestPath, "utf8"));
2656
+ const graph = JSON.parse(readFileSync4(manifestPath, "utf8"));
2698
2657
  return {
2699
2658
  agents: Array.isArray(graph.runtimeAgents) ? graph.runtimeAgents.length : 0,
2700
2659
  artifacts: Array.isArray(graph.artifacts) ? graph.artifacts.length : 0
@@ -2713,6 +2672,7 @@ function resumeFlagsForInit(flags) {
2713
2672
  const passthrough = [
2714
2673
  "profile",
2715
2674
  "tenant-id",
2675
+ "verification-id",
2716
2676
  "offline",
2717
2677
  "analyst",
2718
2678
  "sdk-integrate",