@moda-ai/cli 1.18.0 → 1.19.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.
package/dist/cli.js CHANGED
@@ -177,6 +177,7 @@ Commands:
177
177
  prompts sync --watch Upload again when prompt files change
178
178
  prompts promote <key> Move a dev/staging/prod label
179
179
  prompts ab Run a judged prompt A/B replay experiment
180
+ prompts propose Revise a prompt from a run's failures (--gate to auto-A/B, --promote-on-win)
180
181
 
181
182
  Skills:
182
183
  skills gen Kick off tenant-wide skill generation
@@ -249,6 +250,10 @@ Init flags:
249
250
  --no-ci-cd Skip CI/CD workflow creation
250
251
  --autosync Create GitHub Actions workflow to sync prompts + skills to Moda on push to main
251
252
  --no-autosync Skip autosync workflow creation
253
+ --harness-rescan Create GitHub Actions workflow to re-scan the harness and sync it to Moda on push to main
254
+ --no-harness-rescan Skip harness rescan workflow creation
255
+ --harness-rescan-paths=GLOBS Comma-separated path globs that scope which pushes trigger the
256
+ harness rescan workflow (implies --harness-rescan)
252
257
  --offline Run final validation offline (skip the online telemetry check)
253
258
 
254
259
  Provision flags (headless; requires an existing "moda auth login" session):
@@ -291,6 +296,7 @@ Examples:
291
296
  moda harness explain --agent=agent_support
292
297
  moda init --ci-cd
293
298
  moda init --autosync
299
+ moda init --harness-rescan --harness-rescan-paths='src/agents/**'
294
300
  moda overview --days-back=30
295
301
  moda clusters --time-range=7d
296
302
  moda conversations --search="error" --limit=5
@@ -706,7 +712,7 @@ export const commandRegistry = createCommandRegistry([
706
712
  {
707
713
  name: 'init',
708
714
  description: 'Set up Moda in your project',
709
- examples: ['moda init', 'moda init --sdk-integrate', 'moda init --ci-cd', 'moda init --autosync'],
715
+ examples: ['moda init', 'moda init --sdk-integrate', 'moda init --ci-cd', 'moda init --autosync', 'moda init --harness-rescan'],
710
716
  auth: 'none',
711
717
  outputModes: ['human', 'json', 'agent'],
712
718
  mutability: 'write',
package/dist/config.d.ts CHANGED
@@ -28,6 +28,18 @@ export declare function saveCliConfig(update: CliConfig, options?: ProfileIOOpti
28
28
  * long-lived process (or a test) sees the current value.
29
29
  */
30
30
  export declare function resolveApiKey(options?: ProfileIOOptions): string | undefined;
31
+ /**
32
+ * Signed CLI keys embed their tenant: the format is
33
+ * moda_sk_<base64url("v1:keyId:tenantId:timestamp:signature")> — see
34
+ * createApiKey/parseAndVerifySignedKey in
35
+ * apps/moda-backend/src/api-keys/api-keys.service.ts. The server derives the
36
+ * upload destination tenant from the key alone, so this is the only
37
+ * client-side tenant source that matches where data actually lands
38
+ * (config / MODA_TENANT_ID can disagree with a shadowing env key). The HMAC
39
+ * signature can only be verified server-side; treat the result as
40
+ * informational.
41
+ */
42
+ export declare function embeddedTenantIdFromApiKey(key: string): string | undefined;
31
43
  /**
32
44
  * Resolve the ingest worker base URL used by `moda workspace snapshot`.
33
45
  * Env override -> active profile -> legacy config -> production default.
package/dist/config.js CHANGED
@@ -40,6 +40,34 @@ export function resolveApiKey(options = {}) {
40
40
  loadCliConfig(options).api_key ||
41
41
  undefined);
42
42
  }
43
+ const SIGNED_API_KEY_PREFIX = 'moda_sk_';
44
+ const SIGNED_API_KEY_VERSION = 'v1';
45
+ /**
46
+ * Signed CLI keys embed their tenant: the format is
47
+ * moda_sk_<base64url("v1:keyId:tenantId:timestamp:signature")> — see
48
+ * createApiKey/parseAndVerifySignedKey in
49
+ * apps/moda-backend/src/api-keys/api-keys.service.ts. The server derives the
50
+ * upload destination tenant from the key alone, so this is the only
51
+ * client-side tenant source that matches where data actually lands
52
+ * (config / MODA_TENANT_ID can disagree with a shadowing env key). The HMAC
53
+ * signature can only be verified server-side; treat the result as
54
+ * informational.
55
+ */
56
+ export function embeddedTenantIdFromApiKey(key) {
57
+ if (!key.startsWith(SIGNED_API_KEY_PREFIX))
58
+ return undefined;
59
+ try {
60
+ const decoded = Buffer.from(key.slice(SIGNED_API_KEY_PREFIX.length), 'base64url').toString('utf8');
61
+ // Expected format: version:keyId:tenantId:timestamp:signature
62
+ const parts = decoded.split(':');
63
+ if (parts.length !== 5 || parts[0] !== SIGNED_API_KEY_VERSION)
64
+ return undefined;
65
+ return parts[2] || undefined;
66
+ }
67
+ catch {
68
+ return undefined;
69
+ }
70
+ }
43
71
  /**
44
72
  * Resolve the ingest worker base URL used by `moda workspace snapshot`.
45
73
  * Env override -> active profile -> legacy config -> production default.
package/dist/harness.d.ts CHANGED
@@ -390,6 +390,51 @@ declare function buildCommandScanPlan(context: CommandContext, rootDir: string):
390
390
  };
391
391
  export declare function writeHumanProgress(context: CommandContext, message: string): void;
392
392
  export declare function readHarnessSyncState(rootDir: string): HarnessSyncState | undefined;
393
+ /**
394
+ * The identity a sync-style upload will actually authenticate as, resolved
395
+ * with the same precedence as the API call itself (MODA_API_KEY env wins over
396
+ * the active profile / legacy config). Used for the pre-upload
397
+ * "Syncing as <credential> → tenant <id>" banner so a shadowed or stale key
398
+ * is visible BEFORE data lands in the wrong tenant.
399
+ *
400
+ * The destination tenant is derived from the WINNING KEY itself (signed CLI
401
+ * keys embed their tenant, and the server resolves the destination from the
402
+ * key alone) — never from flag/env/profile config, which is exactly what a
403
+ * shadowing stale key disagrees with. When the key does not embed a tenant,
404
+ * the config-resolved id is returned with `tenantSource: 'config'` so
405
+ * callers must label it unverified. Best-effort: never throws (a broken
406
+ * MODA_PROFILE must not abort onboarding).
407
+ */
408
+ export declare function describeSyncIdentity(options?: {
409
+ profile?: string;
410
+ env?: NodeJS.ProcessEnv;
411
+ }): {
412
+ credential: string;
413
+ tenantId?: string;
414
+ /** 'api-key' = decoded from the winning key (authoritative); 'config' = flag/env/profile fallback (unverified). */
415
+ tenantSource?: 'api-key' | 'config';
416
+ } | undefined;
417
+ /**
418
+ * Human tenant label for the identity banner: key-derived ids are printed
419
+ * bare; config-derived ids are explicitly marked unverified so the banner
420
+ * never asserts a destination the server might not use.
421
+ */
422
+ export declare function describeSyncTenantLabel(identity: {
423
+ tenantId?: string;
424
+ tenantSource?: 'api-key' | 'config';
425
+ }): string;
426
+ /**
427
+ * Reports in subdirectories that are FRESHER than the root's
428
+ * `.moda/harness-report.json` (a missing root report counts as infinitely
429
+ * old). Catches the `--path` trap: `harness analyze --path=<subdir>` writes
430
+ * `<subdir>/.moda/harness-report.json`, and a later root-level
431
+ * `harness approve`/`sync` silently targets the stale (or absent) root report.
432
+ * Newest first.
433
+ */
434
+ export declare function findFresherHarnessReports(rootDir: string): Array<{
435
+ path: string;
436
+ mtimeMs: number;
437
+ }>;
393
438
  export declare function writeHarnessArtifacts(graph: HarnessGraph, rootDir: string, options?: {
394
439
  summary?: boolean;
395
440
  }): string[];
package/dist/harness.js CHANGED
@@ -6,7 +6,7 @@ import { basename, delimiter, dirname, extname, join, relative, resolve, sep } f
6
6
  import { confirm, select } from '@inquirer/prompts';
7
7
  import { callControlAPI } from './api-client.js';
8
8
  import { AnalystTui, shouldUseAnalystTui } from './analyst-tui.js';
9
- import { resolveApiKey, resolveTenantId } from './config.js';
9
+ import { embeddedTenantIdFromApiKey, resolveApiKey, resolveTenantId } from './config.js';
10
10
  import { CliInputError, CliInputRequiredError } from './errors.js';
11
11
  import { HARNESS_REMOTE_RUN_STATE_PATH, isRemoteHarnessAnalyze, readRemoteRunState, runHarnessPull, runRemoteHarnessAnalyst, } from './harness-remote.js';
12
12
  import { getActiveProfile } from './profiles.js';
@@ -101,6 +101,24 @@ export async function runHarnessCommand(context) {
101
101
  if (!resolveApiKey({ profile: context.profile, env: context.env }) && !context.dryRun) {
102
102
  throw new CliInputError('No Moda API key found for harness sync.', 'Run `moda init` first, set MODA_API_KEY, or select a profile with an API key.');
103
103
  }
104
+ warnFresherSubdirHarnessReports(context, rootDir, context.flags['from-report'] ?? context.flags.report);
105
+ if (!context.dryRun) {
106
+ const identity = describeSyncIdentity({ profile: context.profile, env: context.env });
107
+ if (identity) {
108
+ // The banner's tenant comes from the winning key when it embeds
109
+ // one. Otherwise fall back to the flag/env/profile-resolved tenant
110
+ // but label it unverified — that source is precisely what a
111
+ // shadowing stale key disagrees with, so it must never be asserted
112
+ // as the destination.
113
+ const label = identity.tenantSource === 'api-key'
114
+ ? describeSyncTenantLabel(identity)
115
+ : describeSyncTenantLabel({
116
+ tenantId: tenant?.tenantId ?? identity.tenantId,
117
+ tenantSource: 'config',
118
+ });
119
+ writeHumanProgress(context, `Syncing as ${identity.credential} → tenant ${label}`);
120
+ }
121
+ }
104
122
  if (context.flags['from-report'] || context.flags.report) {
105
123
  const reportPath = resolveHarnessReportPath(rootDir, context.flags['from-report'] ?? context.flags.report);
106
124
  const { report, validation, approval, approved } = loadHarnessReportForSync(rootDir, reportPath);
@@ -344,6 +362,7 @@ export async function runHarnessCommand(context) {
344
362
  return { exitCode: validation.status === 'pass' ? 0 : 1 };
345
363
  }
346
364
  case 'approve': {
365
+ warnFresherSubdirHarnessReports(context, rootDir, context.positionals[1] ?? context.flags.report);
347
366
  const reportPath = resolveHarnessReportPath(rootDir, context.positionals[1] ?? context.flags.report);
348
367
  const approval = await approveHarnessReport({
349
368
  rootDir,
@@ -363,7 +382,7 @@ export async function runHarnessCommand(context) {
363
382
  return { exitCode: 0 };
364
383
  }
365
384
  case 'agents': {
366
- const graph = loadOrScanHarness(rootDir, tenant);
385
+ const graph = loadOrScanHarness(rootDir, tenant, context);
367
386
  context.output.writeData({
368
387
  summary: summarizeHarness(graph),
369
388
  agentFamilies: graph.agentFamilies,
@@ -373,12 +392,12 @@ export async function runHarnessCommand(context) {
373
392
  return { exitCode: 0 };
374
393
  }
375
394
  case 'graph': {
376
- const graph = loadOrScanHarness(rootDir, tenant);
395
+ const graph = loadOrScanHarness(rootDir, tenant, context);
377
396
  context.output.writeData(graph);
378
397
  return { exitCode: 0 };
379
398
  }
380
399
  case 'explain': {
381
- const graph = loadOrScanHarness(rootDir, tenant);
400
+ const graph = loadOrScanHarness(rootDir, tenant, context);
382
401
  if (selectedAgentId) {
383
402
  const agent = graph.runtimeAgents.find((candidate) => candidate.id === selectedAgentId);
384
403
  if (!agent) {
@@ -2040,15 +2059,187 @@ function selectedRuntimeAgentId(flags) {
2040
2059
  return flags.agent;
2041
2060
  return undefined;
2042
2061
  }
2043
- function loadOrScanHarness(rootDir, tenant) {
2062
+ function loadOrScanHarness(rootDir, tenant, context) {
2044
2063
  const manifestPath = join(rootDir, HARNESS_PATH);
2045
- return existsSync(manifestPath)
2046
- ? readHarnessGraph(manifestPath)
2047
- : scanHarness({
2048
- rootDir,
2049
- tenantId: tenant?.tenantId,
2050
- tenantSource: tenant?.source,
2051
- });
2064
+ if (existsSync(manifestPath))
2065
+ return readHarnessGraph(manifestPath);
2066
+ // Provenance banner: without a persisted manifest this command output comes
2067
+ // from a live scan of the current directory, which is easy to mistake for
2068
+ // synced/account data (especially right after switching repos or profiles).
2069
+ if (context) {
2070
+ writeHarnessNotice(context, `No ${HARNESS_PATH} found; showing a live scan of ${rootDir}. ` +
2071
+ 'This is local scan output, NOT synced or account data. ' +
2072
+ 'Run `moda harness scan` to persist it and `moda harness sync` to upload it.');
2073
+ }
2074
+ return scanHarness({
2075
+ rootDir,
2076
+ tenantId: tenant?.tenantId,
2077
+ tenantSource: tenant?.source,
2078
+ });
2079
+ }
2080
+ /**
2081
+ * Warning line that must be visible in every output mode: styled progress on
2082
+ * stderr for humans, a plain `Warning:` stderr line for machine modes (stdout
2083
+ * stays machine-readable JSON either way). Quiet suppresses it like the rest
2084
+ * of the harness progress surface.
2085
+ */
2086
+ function writeHarnessNotice(context, message) {
2087
+ if (context.quiet && context.flags.progress !== 'true')
2088
+ return;
2089
+ if (context.outputMode === 'human') {
2090
+ console.error(renderHarnessProgress(message, 'warning'));
2091
+ return;
2092
+ }
2093
+ console.error(`Warning: ${message}`);
2094
+ }
2095
+ /**
2096
+ * The identity a sync-style upload will actually authenticate as, resolved
2097
+ * with the same precedence as the API call itself (MODA_API_KEY env wins over
2098
+ * the active profile / legacy config). Used for the pre-upload
2099
+ * "Syncing as <credential> → tenant <id>" banner so a shadowed or stale key
2100
+ * is visible BEFORE data lands in the wrong tenant.
2101
+ *
2102
+ * The destination tenant is derived from the WINNING KEY itself (signed CLI
2103
+ * keys embed their tenant, and the server resolves the destination from the
2104
+ * key alone) — never from flag/env/profile config, which is exactly what a
2105
+ * shadowing stale key disagrees with. When the key does not embed a tenant,
2106
+ * the config-resolved id is returned with `tenantSource: 'config'` so
2107
+ * callers must label it unverified. Best-effort: never throws (a broken
2108
+ * MODA_PROFILE must not abort onboarding).
2109
+ */
2110
+ export function describeSyncIdentity(options = {}) {
2111
+ const apiKey = resolveApiKey(options);
2112
+ if (!apiKey)
2113
+ return undefined;
2114
+ const redacted = apiKey.length > 12
2115
+ ? `${apiKey.slice(0, 8)}…${apiKey.slice(-4)}`
2116
+ : `…${apiKey.slice(-4)}`;
2117
+ const env = options.env ?? process.env;
2118
+ // getActiveProfile throws when the named profile is missing (e.g. a stale
2119
+ // MODA_PROFILE) even though the key already resolved via legacy config —
2120
+ // the banner is advisory and must never abort the command around it.
2121
+ let profileName;
2122
+ try {
2123
+ profileName = getActiveProfile(options).name;
2124
+ }
2125
+ catch {
2126
+ profileName = undefined;
2127
+ }
2128
+ const credential = env.MODA_API_KEY === apiKey
2129
+ ? `MODA_API_KEY env key (${redacted})`
2130
+ : profileName
2131
+ ? `profile '${profileName}' key (${redacted})`
2132
+ : `stored key (${redacted})`;
2133
+ const keyTenantId = embeddedTenantIdFromApiKey(apiKey);
2134
+ if (keyTenantId) {
2135
+ return { credential, tenantId: keyTenantId, tenantSource: 'api-key' };
2136
+ }
2137
+ const configTenantId = resolveTenantId(options);
2138
+ return {
2139
+ credential,
2140
+ tenantId: configTenantId,
2141
+ tenantSource: configTenantId ? 'config' : undefined,
2142
+ };
2143
+ }
2144
+ /**
2145
+ * Human tenant label for the identity banner: key-derived ids are printed
2146
+ * bare; config-derived ids are explicitly marked unverified so the banner
2147
+ * never asserts a destination the server might not use.
2148
+ */
2149
+ export function describeSyncTenantLabel(identity) {
2150
+ if (!identity.tenantId)
2151
+ return 'unresolved';
2152
+ if (identity.tenantSource === 'api-key')
2153
+ return identity.tenantId;
2154
+ return `${identity.tenantId} (unverified — from local config, not the API key; the server derives the destination tenant from the key)`;
2155
+ }
2156
+ /** Bounded walk looking for `<subdir>/.moda/harness-report.json` files. */
2157
+ const FRESHER_REPORT_MAX_DEPTH = 4;
2158
+ const FRESHER_REPORT_MAX_DIRS = 4_000;
2159
+ /**
2160
+ * Reports in subdirectories that are FRESHER than the root's
2161
+ * `.moda/harness-report.json` (a missing root report counts as infinitely
2162
+ * old). Catches the `--path` trap: `harness analyze --path=<subdir>` writes
2163
+ * `<subdir>/.moda/harness-report.json`, and a later root-level
2164
+ * `harness approve`/`sync` silently targets the stale (or absent) root report.
2165
+ * Newest first.
2166
+ */
2167
+ export function findFresherHarnessReports(rootDir) {
2168
+ let rootMtimeMs = 0;
2169
+ try {
2170
+ rootMtimeMs = lstatSync(join(rootDir, HARNESS_REPORT_PATH)).mtimeMs;
2171
+ }
2172
+ catch {
2173
+ rootMtimeMs = 0;
2174
+ }
2175
+ const fresher = [];
2176
+ let visited = 0;
2177
+ const walk = (dir, depth) => {
2178
+ if (depth > FRESHER_REPORT_MAX_DEPTH || visited >= FRESHER_REPORT_MAX_DIRS)
2179
+ return;
2180
+ visited += 1;
2181
+ let entries;
2182
+ try {
2183
+ entries = readdirSync(dir, { withFileTypes: true });
2184
+ }
2185
+ catch {
2186
+ return;
2187
+ }
2188
+ for (const entry of entries) {
2189
+ // Dot-dirs (including each dir's own `.moda`, checked explicitly below)
2190
+ // and scan-skipped dirs never host a user project subtree.
2191
+ if (!entry.isDirectory() || entry.name.startsWith('.') || SKIPPED_DIRS.has(entry.name))
2192
+ continue;
2193
+ const child = join(dir, entry.name);
2194
+ try {
2195
+ const candidate = lstatSync(join(child, HARNESS_REPORT_PATH));
2196
+ if (candidate.isFile() && candidate.mtimeMs > rootMtimeMs) {
2197
+ fresher.push({
2198
+ path: normalizePath(relative(rootDir, join(child, HARNESS_REPORT_PATH))),
2199
+ mtimeMs: candidate.mtimeMs,
2200
+ });
2201
+ }
2202
+ }
2203
+ catch {
2204
+ // No report in this subdir; keep walking.
2205
+ }
2206
+ walk(child, depth + 1);
2207
+ }
2208
+ };
2209
+ walk(rootDir, 1);
2210
+ return fresher.sort((a, b) => b.mtimeMs - a.mtimeMs);
2211
+ }
2212
+ /**
2213
+ * `--path` trap guard for `harness approve` / `harness sync`: when the
2214
+ * report the command is about to use is older than a fresher
2215
+ * `<subdir>/.moda/harness-report.json`, point at the fresher one. Skipped
2216
+ * when the user explicitly named a report file (they already chose).
2217
+ */
2218
+ function warnFresherSubdirHarnessReports(context, rootDir, explicitReport) {
2219
+ // Only treat an explicit report as an opt-out when it points somewhere other
2220
+ // than the default root report. `--from-report=.moda/harness-report.json`
2221
+ // still targets the default report, so the fresher-subdir warning must fire.
2222
+ const explicitReportPath = explicitReport && explicitReport !== 'true'
2223
+ ? normalizePath(relative(rootDir, resolve(rootDir, explicitReport)))
2224
+ : undefined;
2225
+ if (explicitReportPath && explicitReportPath !== normalizePath(HARNESS_REPORT_PATH))
2226
+ return;
2227
+ let fresher;
2228
+ try {
2229
+ fresher = findFresherHarnessReports(rootDir);
2230
+ }
2231
+ catch {
2232
+ return;
2233
+ }
2234
+ if (fresher.length === 0)
2235
+ return;
2236
+ const newest = fresher[0];
2237
+ const subdir = normalizePath(dirname(dirname(newest.path)));
2238
+ const rootState = existsSync(join(rootDir, HARNESS_REPORT_PATH))
2239
+ ? `older ${HARNESS_REPORT_PATH}`
2240
+ : `no ${HARNESS_REPORT_PATH}`;
2241
+ writeHarnessNotice(context, `A fresher harness report exists at ${newest.path} (this directory has ${rootState}). ` +
2242
+ `If you meant that report, re-run with --path=${subdir}.`);
2052
2243
  }
2053
2244
  function readHarnessGraph(manifestPath) {
2054
2245
  return JSON.parse(readFileSync(manifestPath, 'utf8'));
@@ -11,7 +11,7 @@ export interface InitPhaseResult {
11
11
  details?: string;
12
12
  }
13
13
  export interface InitSetupAction {
14
- category: 'sdk' | 'harness' | 'hooks' | 'prompts' | 'skills' | 'ci-cd' | 'autosync' | 'validation';
14
+ category: 'sdk' | 'harness' | 'hooks' | 'prompts' | 'skills' | 'ci-cd' | 'autosync' | 'harness-rescan' | 'validation';
15
15
  title: string;
16
16
  status: 'planned' | 'applied' | 'skipped' | 'manual';
17
17
  approved: boolean;
@@ -28,6 +28,44 @@ 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
+ export declare function writeCiCdWorkflow(cwd?: string): string;
32
+ export declare function writeAutosyncWorkflow(cwd?: string): string;
33
+ export declare function writeHarnessRescanWorkflow(cwd?: string, paths?: string[]): string;
34
+ /**
35
+ * Signals from the discovered graph used by the init 0-agent quality gate:
36
+ * runtime agent count plus the LLM framework/provider names detected in the
37
+ * repo.
38
+ */
39
+ export interface HarnessQualitySignals {
40
+ runtimeAgents: number;
41
+ frameworks: string[];
42
+ providers: string[];
43
+ }
44
+ /**
45
+ * Read the gate signals from the SAME graph the auto-sync uploads: the cited
46
+ * report (`.moda/harness-report.json`, consumed by `harness sync --from-report`)
47
+ * is authoritative, so read its `graph` first. Only fall back to the standalone
48
+ * manifest (`.moda/harness.json`) when no report exists, otherwise a stale or
49
+ * missing manifest could let an empty report slip past the gate and overwrite
50
+ * the tenant's harness. Returns undefined when neither is readable.
51
+ */
52
+ export declare function readHarnessQualitySignals(cwd?: string): HarnessQualitySignals | undefined;
53
+ /**
54
+ * True when discovery found no runtime agents even though the repo carries
55
+ * LLM framework/provider signals — the signature of a failed discovery run,
56
+ * not an agent-free repo.
57
+ */
58
+ export declare function harnessLooksIncomplete(signals: HarnessQualitySignals): boolean;
59
+ /**
60
+ * Deterministic repo-side LLM signals: well-known LLM package names found in
61
+ * dependency manifests. Independent of the discovery output, so a fully
62
+ * failed discovery (empty graph) cannot bypass the 0-agent gate.
63
+ */
64
+ export declare function detectRepoLlmSignals(cwd?: string): {
65
+ frameworks: string[];
66
+ providers: string[];
67
+ };
68
+ export declare function resumeFlagsForInit(flags: Record<string, unknown>): string[];
31
69
  /**
32
70
  * Onboarding runs harness discovery SERVER-SIDE by default so the customer
33
71
  * needs no local coding agent. Opt out with `--local`, `--no-remote`,