@moda-ai/cli 1.36.0 → 1.38.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-3kcy61fr.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,26 @@ 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-k38j2fpq.js";
35
38
  import {
39
+ ConnectCodeError,
40
+ connectWithCode,
41
+ loadAuthSession,
36
42
  loadToken,
37
43
  login
38
- } from "./cli-yz99t7n8.js";
44
+ } from "./cli-kegd748v.js";
39
45
  import {
40
46
  CliAuthError,
41
47
  CliInputError,
@@ -43,154 +49,47 @@ import {
43
49
  createCommandContext,
44
50
  resolveIngestUrl,
45
51
  resolveModaBaseUrl
46
- } from "./cli-drabjmaj.js";
52
+ } from "./cli-q79sq80a.js";
53
+ import"./cli-0v6na3yp.js";
47
54
 
48
55
  // 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";
56
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
57
+ import { dirname as dirname3, join as join5 } from "node:path";
51
58
  import { checkbox, confirm, select } from "@inquirer/prompts";
52
59
 
53
60
  // 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
- }
61
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
62
+ import { dirname, join as join2 } from "node:path";
164
63
 
165
64
  // src/init/sdk-catalog.ts
166
65
  import {
167
66
  copyFileSync,
168
- existsSync as existsSync2,
169
- mkdirSync as mkdirSync2,
170
- readdirSync as readdirSync2,
67
+ existsSync,
68
+ mkdirSync,
69
+ readdirSync,
171
70
  rmSync,
172
71
  statSync
173
72
  } from "node:fs";
174
- import { join as join2, relative as relative2, resolve } from "node:path";
73
+ import { join, relative, resolve } from "node:path";
175
74
  function materializeSdkCatalog(cwd, runId) {
176
75
  if (!/^[a-z0-9-]+$/.test(runId)) {
177
76
  throw new Error(`Invalid SDK catalog run id: ${JSON.stringify(runId).slice(0, 80)}`);
178
77
  }
179
78
  const sourceDir = bundledSkillsDir();
180
- const indexPath = join2(sourceDir, "index.json");
181
- if (!existsSync2(sourceDir) || !existsSync2(indexPath)) {
79
+ const indexPath = join(sourceDir, "index.json");
80
+ if (!existsSync(sourceDir) || !existsSync(indexPath)) {
182
81
  throw missingBundleError(sourceDir);
183
82
  }
184
83
  const index = readBundledIndex();
185
84
  if (!index) {
186
85
  throw missingBundleError(sourceDir);
187
86
  }
188
- const dir = join2(cwd, ".moda", "tmp", `sdk-catalog-${runId}`);
87
+ const dir = join(cwd, ".moda", "tmp", `sdk-catalog-${runId}`);
189
88
  cleanupSdkCatalog(dir);
190
89
  copyDirectoryRecursive(sourceDir, dir);
191
90
  return {
192
91
  dir,
193
- relativeDir: relative2(cwd, dir).replace(/\\/g, "/"),
92
+ relativeDir: relative(cwd, dir).replace(/\\/g, "/"),
194
93
  index
195
94
  };
196
95
  }
@@ -216,10 +115,10 @@ function renderCatalogListing(index) {
216
115
  `);
217
116
  }
218
117
  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);
118
+ mkdirSync(targetDir, { recursive: true });
119
+ for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
120
+ const source = join(sourceDir, entry.name);
121
+ const target = join(targetDir, entry.name);
223
122
  if (entry.isDirectory()) {
224
123
  copyDirectoryRecursive(source, target);
225
124
  } else if (entry.isFile()) {
@@ -230,9 +129,9 @@ function copyDirectoryRecursive(sourceDir, targetDir) {
230
129
  function missingBundleError(sourceDir) {
231
130
  let detail = "is absent";
232
131
  try {
233
- if (existsSync2(sourceDir) && !statSync(sourceDir).isDirectory()) {
132
+ if (existsSync(sourceDir) && !statSync(sourceDir).isDirectory()) {
234
133
  detail = "is not a directory";
235
- } else if (existsSync2(sourceDir)) {
134
+ } else if (existsSync(sourceDir)) {
236
135
  detail = "has no readable index.json";
237
136
  }
238
137
  } catch {
@@ -245,10 +144,10 @@ function missingBundleError(sourceDir) {
245
144
  var SDK_GUIDE_PATH = ".moda/SDK_INTEGRATION.md";
246
145
  function writeSdkIntegrationGuide(cwd = process.cwd()) {
247
146
  const relativePath = SDK_GUIDE_PATH;
248
- const absolutePath = join3(cwd, relativePath);
147
+ const absolutePath = join2(cwd, relativePath);
249
148
  const content = renderSdkIntegrationGuideContent();
250
- mkdirSync3(dirname2(absolutePath), { recursive: true });
251
- writeFileSync2(absolutePath, content);
149
+ mkdirSync2(dirname(absolutePath), { recursive: true });
150
+ writeFileSync(absolutePath, content);
252
151
  return relativePath;
253
152
  }
254
153
  function renderSdkIntegrationGuideContent() {
@@ -367,11 +266,11 @@ function readRuntimeSummary(cwd) {
367
266
  providers: [],
368
267
  runtimeAgentNames: []
369
268
  };
370
- const path = join3(cwd, ".moda", "harness.json");
371
- if (!existsSync3(path))
269
+ const path = join2(cwd, ".moda", "harness.json");
270
+ if (!existsSync2(path))
372
271
  return empty;
373
272
  try {
374
- const parsed = JSON.parse(readFileSync2(path, "utf8"));
273
+ const parsed = JSON.parse(readFileSync(path, "utf8"));
375
274
  if (!isRecord(parsed))
376
275
  return empty;
377
276
  const runtimeAgents = Array.isArray(parsed.runtimeAgents) ? parsed.runtimeAgents : [];
@@ -415,22 +314,22 @@ function isRecord(value) {
415
314
  import { randomBytes as randomBytes2 } from "node:crypto";
416
315
  import {
417
316
  closeSync,
418
- existsSync as existsSync5,
419
- mkdirSync as mkdirSync4,
317
+ existsSync as existsSync4,
318
+ mkdirSync as mkdirSync3,
420
319
  openSync,
421
- readFileSync as readFileSync4,
320
+ readFileSync as readFileSync3,
422
321
  readSync,
423
- readdirSync as readdirSync3,
322
+ readdirSync as readdirSync2,
424
323
  rmSync as rmSync2,
425
324
  statSync as statSync2,
426
- writeFileSync as writeFileSync3
325
+ writeFileSync as writeFileSync2
427
326
  } from "node:fs";
428
- import { dirname as dirname3, join as join5, relative as relative3, resolve as resolve2 } from "node:path";
327
+ import { dirname as dirname2, join as join4, relative as relative2, resolve as resolve2 } from "node:path";
429
328
  import { spawnSync } from "node:child_process";
430
329
 
431
330
  // src/init/sdk-protocol.ts
432
- import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
433
- import { join as join4 } from "node:path";
331
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
332
+ import { join as join3 } from "node:path";
434
333
  import { z } from "zod";
435
334
  var SDK_RESULT_SCHEMA_VERSION = "moda.sdk_integration_result.v1";
436
335
  var SDK_RESULT_JSON_BEGIN = "MODA_SDK_RESULT_JSON_BEGIN";
@@ -522,10 +421,10 @@ var SdkAgentResultSchema = z.object({
522
421
  });
523
422
  function extractSdkAgentResult(opts) {
524
423
  const diagnostics = [];
525
- const resultPath = join4(opts.cwd, SDK_RESULT_FILE);
526
- if (existsSync4(resultPath)) {
424
+ const resultPath = join3(opts.cwd, SDK_RESULT_FILE);
425
+ if (existsSync3(resultPath)) {
527
426
  try {
528
- const parsed = parseCandidate(readFileSync3(resultPath, "utf8"));
427
+ const parsed = parseCandidate(readFileSync2(resultPath, "utf8"));
529
428
  if (parsed.result)
530
429
  return { result: parsed.result, source: "file" };
531
430
  diagnostics.push(`result file: ${parsed.detail}`);
@@ -585,6 +484,12 @@ var MAX_TIMEOUT_MS = 600000;
585
484
  function generateVerificationId() {
586
485
  return `moda-verify-${randomBytes(16).toString("hex")}`;
587
486
  }
487
+ function isModaVerificationId(value) {
488
+ return /^moda-verify-[0-9a-f]{32}$/.test(value);
489
+ }
490
+ function isAcceptedVerificationId(value) {
491
+ return isModaVerificationId(value) || /^onb_[A-Za-z0-9]{16}$/.test(value);
492
+ }
588
493
  async function verifyExactTrace(opts) {
589
494
  const now = opts.now ?? Date.now;
590
495
  const sleep = opts.sleep ?? ((ms) => new Promise((resolve2) => setTimeout(resolve2, ms)));
@@ -776,7 +681,7 @@ async function runSdkIntegration(options = {}) {
776
681
  const verify = options.verify ?? verifyExactTrace;
777
682
  const maxRepairAttempts = resolveMaxRepairAttempts(options.maxRepairAttempts);
778
683
  const runId = randomBytes2(4).toString("hex");
779
- const verificationId = generateVerificationId();
684
+ const verificationId = options.verificationId ?? generateVerificationId();
780
685
  let catalog;
781
686
  let gitBaseline = null;
782
687
  let runStartedAt = Date.now();
@@ -1162,7 +1067,7 @@ function scanForSecrets(cwd, paths, apiKey) {
1162
1067
  const absPath = resolve2(cwd, relPath);
1163
1068
  if (!absPath.startsWith(resolvedCwd + "/") && absPath !== resolvedCwd)
1164
1069
  continue;
1165
- if (!existsSync5(absPath))
1070
+ if (!existsSync4(absPath))
1166
1071
  continue;
1167
1072
  let stat;
1168
1073
  try {
@@ -1174,7 +1079,7 @@ function scanForSecrets(cwd, paths, apiKey) {
1174
1079
  continue;
1175
1080
  let content;
1176
1081
  try {
1177
- content = stat.size > MAX_SCAN_FILE_BYTES ? readFilePrefix(absPath, MAX_SCAN_FILE_BYTES) : readFileSync4(absPath, "utf8");
1082
+ content = stat.size > MAX_SCAN_FILE_BYTES ? readFilePrefix(absPath, MAX_SCAN_FILE_BYTES) : readFileSync3(absPath, "utf8");
1178
1083
  } catch {
1179
1084
  continue;
1180
1085
  }
@@ -1210,7 +1115,7 @@ function collectSecretScanFiles(cwd, paths) {
1210
1115
  const addFile = (path) => {
1211
1116
  if (files.length >= MAX_SCANNED_FILES || !isInsideRepo(path))
1212
1117
  return;
1213
- const relPath = relative3(resolvedCwd, path).replace(/\\/g, "/");
1118
+ const relPath = relative2(resolvedCwd, path).replace(/\\/g, "/");
1214
1119
  if (!relPath || seenFiles.has(relPath))
1215
1120
  return;
1216
1121
  seenFiles.add(relPath);
@@ -1221,13 +1126,13 @@ function collectSecretScanFiles(cwd, paths) {
1221
1126
  return;
1222
1127
  let entries;
1223
1128
  try {
1224
- entries = readdirSync3(dir, { withFileTypes: true });
1129
+ entries = readdirSync2(dir, { withFileTypes: true });
1225
1130
  } catch {
1226
1131
  return;
1227
1132
  }
1228
1133
  for (const entry of entries) {
1229
1134
  if (entry.isFile() && (entry.name === ".env" || entry.name.startsWith(".env."))) {
1230
- addFile(join5(dir, entry.name));
1135
+ addFile(join4(dir, entry.name));
1231
1136
  }
1232
1137
  }
1233
1138
  };
@@ -1240,14 +1145,14 @@ function collectSecretScanFiles(cwd, paths) {
1240
1145
  visitedDirectories += 1;
1241
1146
  let entries;
1242
1147
  try {
1243
- entries = readdirSync3(dir, { withFileTypes: true });
1148
+ entries = readdirSync2(dir, { withFileTypes: true });
1244
1149
  } catch {
1245
1150
  continue;
1246
1151
  }
1247
1152
  for (const entry of entries) {
1248
1153
  if (files.length >= MAX_SCANNED_FILES)
1249
1154
  break;
1250
- const path = join5(dir, entry.name);
1155
+ const path = join4(dir, entry.name);
1251
1156
  if (entry.isDirectory())
1252
1157
  stack.push(path);
1253
1158
  else if (entry.isFile())
@@ -1263,8 +1168,8 @@ function collectSecretScanFiles(cwd, paths) {
1263
1168
  const absPath = resolve2(cwd, relPath);
1264
1169
  if (!isInsideRepo(absPath))
1265
1170
  continue;
1266
- addEnvFiles(dirname3(absPath));
1267
- if (!existsSync5(absPath))
1171
+ addEnvFiles(dirname2(absPath));
1172
+ if (!existsSync4(absPath))
1268
1173
  continue;
1269
1174
  let stat;
1270
1175
  try {
@@ -1353,9 +1258,9 @@ function renderProtocolRetryPrompt(catalogDir) {
1353
1258
  `);
1354
1259
  }
1355
1260
  function removeAgentResultFile(cwd) {
1356
- const path = join5(cwd, SDK_RESULT_FILE);
1261
+ const path = join4(cwd, SDK_RESULT_FILE);
1357
1262
  try {
1358
- if (existsSync5(path))
1263
+ if (existsSync4(path))
1359
1264
  rmSync2(path);
1360
1265
  } catch {}
1361
1266
  }
@@ -1469,9 +1374,9 @@ function composeFinalDetail(status, detail, verification, apiKey, guardsDegraded
1469
1374
  return combined ? redactSecretText(combined, apiKey) : undefined;
1470
1375
  }
1471
1376
  function writeFinalOutcomeArtifact(cwd, payload) {
1472
- const path = join5(cwd, SDK_RESULT_FILE);
1473
- mkdirSync4(dirname3(path), { recursive: true });
1474
- writeFileSync3(path, `${JSON.stringify({
1377
+ const path = join4(cwd, SDK_RESULT_FILE);
1378
+ mkdirSync3(dirname2(path), { recursive: true });
1379
+ writeFileSync2(path, `${JSON.stringify({
1475
1380
  schema_version: OUTCOME_SCHEMA_VERSION,
1476
1381
  status: payload.status,
1477
1382
  verification_id: payload.verification_id,
@@ -1681,13 +1586,38 @@ function visibleLength(value) {
1681
1586
 
1682
1587
  // src/init/index.ts
1683
1588
  var HARNESS_PULL_RECOVERY = "moda harness pull --sync";
1589
+ var HARNESS_SKIP_NEXT = {
1590
+ unavailable: "retry",
1591
+ lost: "pull",
1592
+ unfetched: "pull",
1593
+ failed: "retry"
1594
+ };
1595
+ var HARNESS_SKIP_DETAILS = {
1596
+ unavailable: "hosted analysis unavailable",
1597
+ lost: "lost track of the hosted run",
1598
+ unfetched: "could not fetch the finished run",
1599
+ failed: "hosted run failed"
1600
+ };
1601
+ var HARNESS_SKIP_PLAN = {
1602
+ unavailable: "unavailable",
1603
+ lost: "status unknown",
1604
+ unfetched: "finished but not fetched",
1605
+ failed: "failed"
1606
+ };
1607
+ var HARNESS_SKIP_EVENT = {
1608
+ unavailable: "unavailable",
1609
+ lost: "could not be followed",
1610
+ unfetched: "finished but could not be fetched",
1611
+ failed: "failed on Moda"
1612
+ };
1684
1613
  var LANE_EVENT_THROTTLE_MS = 2000;
1685
1614
  async function runInit(flags, options = {}) {
1686
1615
  const explicitYes = flags.yes === true || flags.yes === "true" || flags.y === true || flags.y === "true";
1687
1616
  const json = flags.json === true || flags.json === "true";
1688
1617
  const agent = flags.agent === true || flags.agent === "true";
1689
1618
  const human = !json && !agent;
1690
- const yes = explicitYes || json || agent;
1619
+ const noPrompts = flags.non_interactive === true || flags.non_interactive === "true";
1620
+ const yes = explicitYes || json || agent || noPrompts;
1691
1621
  const phases = [];
1692
1622
  const log = (message) => {
1693
1623
  if (human)
@@ -1716,8 +1646,34 @@ async function runInit(flags, options = {}) {
1716
1646
  const profile = stringFlag(flags.profile);
1717
1647
  log(renderInitStep(1, 5, "Sign in", "No environment variable files are written. File changes are listed before completion."));
1718
1648
  let token = loadToken({ profile });
1649
+ const connectCode = stringFlag(flagValue(flags, "connect-code", "connect_code"));
1650
+ let codeTenantId;
1651
+ if (connectCode) {
1652
+ const previousEmail = token ? loadAuthSession({ profile })?.user_email : undefined;
1653
+ try {
1654
+ const connected = await connectWithCode(connectCode, {
1655
+ profile,
1656
+ tenantId: stringFlag(flagValue(flags, "tenant-id", "tenant_id"))
1657
+ });
1658
+ token = loadToken({ profile });
1659
+ codeTenantId = connected.tenantId;
1660
+ const email = connected.user.email || "your Moda account";
1661
+ const replaced = previousEmail && previousEmail !== connected.user.email ? ` (replacing the CLI session for ${previousEmail})` : "";
1662
+ log(renderInitNote(`Signed in as ${email} with the one-time code${replaced}`));
1663
+ emit("auth", `Signed in as ${email} with the one-time code${replaced}.`);
1664
+ } catch (error) {
1665
+ if (!(error instanceof ConnectCodeError))
1666
+ throw error;
1667
+ if (error.reason === "workspace_mismatch") {
1668
+ throw new CliInputError(error.message, "Copy the command again from the Moda onboarding page, or drop --tenant-id.");
1669
+ }
1670
+ const next = token ? `Continuing with your existing CLI session${previousEmail ? ` (${previousEmail})` : ""}.` : "Signing in through the browser instead.";
1671
+ warn(renderInitNote(`${error.message} ${next}`, "warning"));
1672
+ emit("auth", `${error.message} ${next}`, "warning");
1673
+ }
1674
+ }
1719
1675
  if (!token) {
1720
- if (json || agent) {
1676
+ if (json || agent || noPrompts) {
1721
1677
  throw new CliAuthError("Browser authentication is required before non-interactive init can continue.", {
1722
1678
  reason: "no_stored_credentials",
1723
1679
  login_command: ["moda", "auth", "login"],
@@ -1725,7 +1681,8 @@ async function runInit(flags, options = {}) {
1725
1681
  }, "Run `moda auth login`, then re-run `moda init`.");
1726
1682
  }
1727
1683
  let action = "login";
1728
- if (!yes) {
1684
+ const fromDashboard = Boolean(connectCode || stringFlag(flagValue(flags, "tenant-id", "tenant_id")) || stringFlag(flagValue(flags, "verification-id", "verification_id")));
1685
+ if (!yes && !fromDashboard) {
1729
1686
  action = await select({
1730
1687
  message: "Get started",
1731
1688
  choices: [
@@ -1746,11 +1703,18 @@ async function runInit(flags, options = {}) {
1746
1703
  log(renderInitStep(2, 5, "Workspace"));
1747
1704
  emit("workspace", "Selecting workspace and creating API key.");
1748
1705
  const tenantIdFlag = stringFlag(flagValue(flags, "tenant-id", "tenant_id"));
1706
+ if (codeTenantId && tenantIdFlag && tenantIdFlag !== codeTenantId) {
1707
+ throw new CliInputError("The sign-in code is for a different workspace than --tenant-id.", "Copy the command again from the Moda onboarding page, or drop --tenant-id.");
1708
+ }
1709
+ const verificationId = stringFlag(flagValue(flags, "verification-id", "verification_id"));
1710
+ if (verificationId !== undefined && !isAcceptedVerificationId(verificationId)) {
1711
+ 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.");
1712
+ }
1749
1713
  const tenantIdEnv = stringFlag(process.env.MODA_TENANT_ID);
1750
1714
  const { apiKey, tenantId: selectedTenantId, tenantName } = await selectTenantAndCreateKey({
1751
- tenantId: tenantIdFlag ?? tenantIdEnv,
1752
- tenantIdSource: tenantIdFlag ? "flag" : tenantIdEnv ? "env" : undefined,
1753
- nonInteractive: json || agent,
1715
+ tenantId: tenantIdFlag ?? codeTenantId ?? tenantIdEnv,
1716
+ tenantIdSource: tenantIdFlag || codeTenantId ? "flag" : tenantIdEnv ? "env" : undefined,
1717
+ nonInteractive: json || agent || noPrompts,
1754
1718
  silent: !human,
1755
1719
  resumeCommand: ["moda", "init", ...resumeFlagsForInit(flags)],
1756
1720
  profile
@@ -1854,6 +1818,8 @@ async function runInit(flags, options = {}) {
1854
1818
  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
1819
  const rootDir = process.cwd();
1856
1820
  const allowLocalFallback = !isTruthyFlag(flagValue(flags, "remote"));
1821
+ const launchLocalAnalyst = human && !noPrompts || stringFlag(flags.analyst) !== undefined;
1822
+ const HARNESS_RETRY = "moda harness analyze --remote --summary --experimental";
1857
1823
  const nestedHarnessFlags = (extra) => commandFlags({
1858
1824
  summary: true,
1859
1825
  yes: true,
@@ -1886,6 +1852,23 @@ async function runInit(flags, options = {}) {
1886
1852
  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
1853
  let harnessPhase = { id: "harness", title: "Harness discovery", status: "completed" };
1888
1854
  let harnessDeferred = false;
1855
+ let harnessSkipped = false;
1856
+ let harnessSkip = "unavailable";
1857
+ const skipHarness = (error, kind = "unavailable") => {
1858
+ harnessSkipped = true;
1859
+ harnessDeferred = true;
1860
+ harnessSkip = kind;
1861
+ const reason = error instanceof Error ? error.message : String(error);
1862
+ const next = HARNESS_SKIP_NEXT[kind] === "pull" ? HARNESS_PULL_RECOVERY : HARNESS_RETRY;
1863
+ harnessPhase = {
1864
+ id: "harness",
1865
+ title: "Harness discovery",
1866
+ status: "warning",
1867
+ details: `skipped: ${HARNESS_SKIP_DETAILS[kind]}`
1868
+ };
1869
+ markApplied(setupPlan, "harness", `Hosted analysis ${HARNESS_SKIP_PLAN[kind]}; run \`${next}\` later.`);
1870
+ emit("harness", `Hosted harness discovery ${HARNESS_SKIP_EVENT[kind]} (${reason}); skipped. Continue with \`${next}\`.`, "warning");
1871
+ };
1889
1872
  const approveAndSyncHarness = async () => {
1890
1873
  const graphSignals = readHarnessQualitySignals(rootDir);
1891
1874
  const repoSignals = detectRepoLlmSignals(rootDir);
@@ -1896,7 +1879,7 @@ async function runInit(flags, options = {}) {
1896
1879
  } : undefined;
1897
1880
  if (signals && harnessLooksIncomplete(signals)) {
1898
1881
  const names = [...signals.frameworks, ...signals.providers].join(", ");
1899
- const retryCommand = remote ? "moda harness analyze --remote --summary" : "moda harness analyze --analyst=claude|cursor|codex --summary";
1882
+ const retryCommand = remote ? "moda harness analyze --remote --summary --experimental" : "moda harness analyze --analyst=claude|cursor|codex --summary";
1900
1883
  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
1884
  warn(` Retry with \`${retryCommand}\`, then \`moda harness approve --yes && moda harness sync --from-report\`.`);
1902
1885
  emit("harness", "Harness discovery found 0 agents despite LLM framework signals; sync withheld.", "warning");
@@ -2003,12 +1986,16 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2003
1986
  } catch (error) {
2004
1987
  if (!allowLocalFallback)
2005
1988
  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
1989
  remote = false;
1990
+ if (launchLocalAnalyst) {
1991
+ warn(renderInitNote(`Server-side harness discovery unavailable (${error instanceof Error ? error.message : String(error)}); falling back to local analyst.`, "warning"));
1992
+ emit("harness", "Server-side discovery failed; falling back to local analyst.", "warning");
1993
+ } else {
1994
+ skipHarness(error);
1995
+ }
2009
1996
  }
2010
1997
  }
2011
- if (!remote) {
1998
+ if (!remote && !harnessSkipped) {
2012
1999
  await finishLocalAnalyze();
2013
2000
  }
2014
2001
  emit("apply", "Applying approved setup changes.");
@@ -2068,6 +2055,7 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2068
2055
  const verificationBaseUrl = resolveModaBaseUrl({ profile });
2069
2056
  sdkIntegration = await runSdkIntegration({
2070
2057
  apiKey,
2058
+ verificationId,
2071
2059
  ingestUrl: resolveIngestUrl({ profile }),
2072
2060
  fetchAudit: (id) => fetchAuditWithApiKey(id, apiKey, verificationBaseUrl),
2073
2061
  onProgress: (message) => {
@@ -2093,17 +2081,21 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2093
2081
  if (remote && pollPromise && remoteRunId) {
2094
2082
  const final = await pollPromise;
2095
2083
  process.removeListener("SIGINT", onRemoteSigint);
2096
- const fallBackToLocal = async (error) => {
2084
+ const fallBackToLocal = async (error, kind) => {
2097
2085
  board?.fail("harness", "hosted analysis failed");
2098
2086
  board?.stop();
2099
2087
  if (!allowLocalFallback)
2100
2088
  throw error;
2089
+ if (!launchLocalAnalyst) {
2090
+ skipHarness(error, kind);
2091
+ return;
2092
+ }
2101
2093
  warn(renderInitNote(`Server-side harness discovery failed (${error instanceof Error ? error.message : String(error)}); falling back to local analyst.`, "warning"));
2102
2094
  emit("harness", "Server-side discovery failed; falling back to local analyst.", "warning");
2103
2095
  await finishLocalAnalyze();
2104
2096
  };
2105
2097
  if (pollError !== undefined) {
2106
- await fallBackToLocal(pollError);
2098
+ await fallBackToLocal(pollError, "lost");
2107
2099
  } else if (!final || final.status !== "completed" && final.status !== "failed") {
2108
2100
  harnessDeferred = true;
2109
2101
  harnessPhase = { id: "harness", title: "Harness discovery", status: "warning", details: "still running on Moda" };
@@ -2123,7 +2115,7 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2123
2115
  flags: nestedHarnessFlags({ "run-id": remoteRunId })
2124
2116
  }));
2125
2117
  } catch (error) {
2126
- await fallBackToLocal(error);
2118
+ await fallBackToLocal(error, isRecoverableRemoteRun(final) ? "unfetched" : "failed");
2127
2119
  }
2128
2120
  if (pullResult) {
2129
2121
  if (pullResult.exitCode !== 0) {
@@ -2175,7 +2167,7 @@ Harness analysis keeps running on Moda. Resume anytime with \`${HARNESS_PULL_REC
2175
2167
  log(renderInitComplete({
2176
2168
  tenantName,
2177
2169
  dashboardUrl: `${resolveModaBaseUrl({ profile })}/dashboard`,
2178
- harness: harnessDeferred ? { state: "pending" } : { state: "mapped", ...readHarnessGraphCounts() },
2170
+ harness: harnessSkipped ? { state: "skipped", reason: harnessSkip, next: HARNESS_SKIP_NEXT[harnessSkip] === "pull" ? HARNESS_PULL_RECOVERY : HARNESS_RETRY } : harnessDeferred ? { state: "pending" } : { state: "mapped", ...readHarnessGraphCounts() },
2179
2171
  filesChanged: changedFiles(setupPlan),
2180
2172
  skipped: setupPlan.filter((item) => item.status === "skipped" || item.status === "manual"),
2181
2173
  telemetrySeen
@@ -2359,9 +2351,9 @@ var GENERATED_WORKFLOW_CLI = `@moda-ai/cli@${VERSION}`;
2359
2351
  var GENERATED_WORKFLOW_NPX = `npx -y --package=${GENERATED_WORKFLOW_CLI} moda`;
2360
2352
  function writeCiCdWorkflow(cwd = process.cwd()) {
2361
2353
  const relativePath = ".github/workflows/moda-validation.yml";
2362
- const path = join6(cwd, relativePath);
2363
- mkdirSync5(dirname4(path), { recursive: true });
2364
- writeFileSync4(path, `${[
2354
+ const path = join5(cwd, relativePath);
2355
+ mkdirSync4(dirname3(path), { recursive: true });
2356
+ writeFileSync3(path, `${[
2365
2357
  "name: Moda Validation",
2366
2358
  "",
2367
2359
  "on:",
@@ -2394,9 +2386,9 @@ function writeCiCdWorkflow(cwd = process.cwd()) {
2394
2386
  }
2395
2387
  function writeAutosyncWorkflow(cwd = process.cwd()) {
2396
2388
  const relativePath = ".github/workflows/moda-autosync.yml";
2397
- const path = join6(cwd, relativePath);
2398
- mkdirSync5(dirname4(path), { recursive: true });
2399
- writeFileSync4(path, `${[
2389
+ const path = join5(cwd, relativePath);
2390
+ mkdirSync4(dirname3(path), { recursive: true });
2391
+ writeFileSync3(path, `${[
2400
2392
  "name: Moda Autosync",
2401
2393
  "",
2402
2394
  "on:",
@@ -2435,8 +2427,8 @@ function writeAutosyncWorkflow(cwd = process.cwd()) {
2435
2427
  }
2436
2428
  function writeHarnessRescanWorkflow(cwd = process.cwd(), paths) {
2437
2429
  const relativePath = ".github/workflows/moda-harness-rescan.yml";
2438
- const path = join6(cwd, relativePath);
2439
- mkdirSync5(dirname4(path), { recursive: true });
2430
+ const path = join5(cwd, relativePath);
2431
+ mkdirSync4(dirname3(path), { recursive: true });
2440
2432
  const pathScope = paths !== undefined && paths.length > 0 ? [
2441
2433
  " # Only pushes touching these paths trigger a rescan.",
2442
2434
  " paths:",
@@ -2446,7 +2438,7 @@ function writeHarnessRescanWorkflow(cwd = process.cwd(), paths) {
2446
2438
  " # paths:",
2447
2439
  " # - 'src/agents/**'"
2448
2440
  ];
2449
- writeFileSync4(path, `${[
2441
+ writeFileSync3(path, `${[
2450
2442
  "name: Moda Harness Rescan",
2451
2443
  "",
2452
2444
  "on:",
@@ -2492,11 +2484,11 @@ function writeHarnessRescanWorkflow(cwd = process.cwd(), paths) {
2492
2484
  return relativePath;
2493
2485
  }
2494
2486
  function printHarnessDetectionSummary(cwd = process.cwd()) {
2495
- const manifestPath = join6(cwd, ".moda", "harness.json");
2496
- if (!existsSync6(manifestPath))
2487
+ const manifestPath = join5(cwd, ".moda", "harness.json");
2488
+ if (!existsSync5(manifestPath))
2497
2489
  return;
2498
2490
  try {
2499
- const graph = JSON.parse(readFileSync5(manifestPath, "utf8"));
2491
+ const graph = JSON.parse(readFileSync4(manifestPath, "utf8"));
2500
2492
  const frameworkNames = (graph.agentFrameworks ?? []).map((framework) => typeof framework.name === "string" ? framework.name : undefined).filter((name) => Boolean(name));
2501
2493
  const providerNames = (graph.modelProviders ?? []).map((provider) => typeof provider.name === "string" ? provider.name : undefined).filter((name) => Boolean(name));
2502
2494
  console.log(` Detected harness: ${(graph.runtimeAgents ?? []).length} runtime agent(s), ` + `${(graph.artifacts ?? []).length} artifact(s), ${(graph.relationships ?? []).length} relationship(s)`);
@@ -2517,20 +2509,20 @@ function signalsFromGraph(graph) {
2517
2509
  };
2518
2510
  }
2519
2511
  function readHarnessQualitySignals(cwd = process.cwd()) {
2520
- const reportPath = join6(cwd, ".moda", "harness-report.json");
2521
- if (existsSync6(reportPath)) {
2512
+ const reportPath = join5(cwd, ".moda", "harness-report.json");
2513
+ if (existsSync5(reportPath)) {
2522
2514
  try {
2523
- const report = JSON.parse(readFileSync5(reportPath, "utf8"));
2515
+ const report = JSON.parse(readFileSync4(reportPath, "utf8"));
2524
2516
  if (report.graph && typeof report.graph === "object") {
2525
2517
  return signalsFromGraph(report.graph);
2526
2518
  }
2527
2519
  } catch {}
2528
2520
  }
2529
- const manifestPath = join6(cwd, ".moda", "harness.json");
2530
- if (!existsSync6(manifestPath))
2521
+ const manifestPath = join5(cwd, ".moda", "harness.json");
2522
+ if (!existsSync5(manifestPath))
2531
2523
  return;
2532
2524
  try {
2533
- return signalsFromGraph(JSON.parse(readFileSync5(manifestPath, "utf8")));
2525
+ return signalsFromGraph(JSON.parse(readFileSync4(manifestPath, "utf8")));
2534
2526
  } catch {
2535
2527
  return;
2536
2528
  }
@@ -2648,7 +2640,7 @@ function detectRepoLlmSignals(cwd = process.cwd()) {
2648
2640
  };
2649
2641
  const scanDir = (dir) => {
2650
2642
  try {
2651
- const pkg = JSON.parse(readFileSync5(join6(dir, "package.json"), "utf8"));
2643
+ const pkg = JSON.parse(readFileSync4(join5(dir, "package.json"), "utf8"));
2652
2644
  for (const dep of [
2653
2645
  ...Object.keys(pkg.dependencies ?? {}),
2654
2646
  ...Object.keys(pkg.devDependencies ?? {}),
@@ -2659,7 +2651,7 @@ function detectRepoLlmSignals(cwd = process.cwd()) {
2659
2651
  } catch {}
2660
2652
  for (const file of ["requirements.txt", "pyproject.toml"]) {
2661
2653
  try {
2662
- const text = readFileSync5(join6(dir, file), "utf8");
2654
+ const text = readFileSync4(join5(dir, file), "utf8");
2663
2655
  for (const name of pythonRequirementNames(text, file)) {
2664
2656
  record(pyDependencySignal(name));
2665
2657
  }
@@ -2674,7 +2666,7 @@ function detectRepoLlmSignals(cwd = process.cwd()) {
2674
2666
  scanDir(dir);
2675
2667
  let entries;
2676
2668
  try {
2677
- entries = readdirSync4(dir, { withFileTypes: true });
2669
+ entries = readdirSync3(dir, { withFileTypes: true });
2678
2670
  } catch {
2679
2671
  return;
2680
2672
  }
@@ -2683,18 +2675,18 @@ function detectRepoLlmSignals(cwd = process.cwd()) {
2683
2675
  continue;
2684
2676
  if (REPO_SIGNAL_SKIPPED_DIRS.has(entry.name))
2685
2677
  continue;
2686
- walk(join6(dir, entry.name), depth + 1);
2678
+ walk(join5(dir, entry.name), depth + 1);
2687
2679
  }
2688
2680
  };
2689
2681
  walk(cwd, 0);
2690
2682
  return { frameworks: [...frameworks].sort(), providers: [...providers].sort() };
2691
2683
  }
2692
2684
  function readHarnessGraphCounts(cwd = process.cwd()) {
2693
- const manifestPath = join6(cwd, ".moda", "harness.json");
2694
- if (!existsSync6(manifestPath))
2685
+ const manifestPath = join5(cwd, ".moda", "harness.json");
2686
+ if (!existsSync5(manifestPath))
2695
2687
  return {};
2696
2688
  try {
2697
- const graph = JSON.parse(readFileSync5(manifestPath, "utf8"));
2689
+ const graph = JSON.parse(readFileSync4(manifestPath, "utf8"));
2698
2690
  return {
2699
2691
  agents: Array.isArray(graph.runtimeAgents) ? graph.runtimeAgents.length : 0,
2700
2692
  artifacts: Array.isArray(graph.artifacts) ? graph.artifacts.length : 0
@@ -2713,6 +2705,7 @@ function resumeFlagsForInit(flags) {
2713
2705
  const passthrough = [
2714
2706
  "profile",
2715
2707
  "tenant-id",
2708
+ "verification-id",
2716
2709
  "offline",
2717
2710
  "analyst",
2718
2711
  "sdk-integrate",