@moda-ai/cli 1.19.2 → 1.20.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
@@ -143,6 +143,7 @@ Commands:
143
143
  harness scan Discover local runtime agents and write .moda/harness.json
144
144
  harness candidates Static (no-LLM) harness candidate pre-pass with file:line citations
145
145
  harness sync Sync the local harness graph to Moda Cloud
146
+ harness delete <id-or-key> Permanently delete a synced harness from Moda Cloud
146
147
  harness agents List detected runtime agents and families
147
148
  harness explain Explain the discovered harness graph
148
149
  agent context Emit local Moda context for coding agents
@@ -284,6 +285,7 @@ Examples:
284
285
  moda harness validate-report
285
286
  moda harness approve --yes
286
287
  moda harness sync --from-report .moda/harness-report.json
288
+ moda harness delete my-harness-key --yes
287
289
  moda --agent doctor
288
290
  moda agent context
289
291
  moda agent next-action
@@ -880,13 +882,14 @@ export const commandRegistry = createCommandRegistry([
880
882
  },
881
883
  {
882
884
  name: 'harness',
883
- description: 'Analyze, validate, approve, sync, and explain the local multi-agent harness graph',
885
+ description: 'Analyze, validate, approve, sync, delete, and explain the local multi-agent harness graph',
884
886
  examples: [
885
887
  'moda harness analyze --summary',
886
888
  'moda harness analyze --remote',
887
889
  'moda harness pull --sync',
888
890
  'moda harness approve --yes',
889
891
  'moda harness sync --from-report .moda/harness-report.json',
892
+ 'moda harness delete my-harness-key --yes',
890
893
  'moda harness explain --agent=agent_support',
891
894
  ],
892
895
  auth: 'none',
package/dist/harness.js CHANGED
@@ -259,6 +259,47 @@ export async function runHarnessCommand(context) {
259
259
  });
260
260
  return { exitCode: 0 };
261
261
  }
262
+ case 'delete': {
263
+ const idOrKey = context.positionals[1]
264
+ ?? (context.flags.harness && context.flags.harness !== 'true' ? context.flags.harness : undefined);
265
+ if (!idOrKey) {
266
+ throw new CliInputError('Missing harness id or key to delete.', 'Usage: moda harness delete <harness-id-or-key> [--yes]');
267
+ }
268
+ const profileOptions = { profile: context.profile, env: context.env };
269
+ if (!resolveApiKey(profileOptions) && !context.dryRun) {
270
+ throw new CliInputError('No Moda API key found for harness delete.', 'Run `moda init` first, set MODA_API_KEY, or select a profile with an API key.');
271
+ }
272
+ // Identity banner (same pattern as sync): deletion is destructive and
273
+ // tenant-scoped, so the resolved credential and destination tenant must
274
+ // be visible BEFORE the user confirms — a shadowed or stale key would
275
+ // otherwise delete from the wrong tenant silently.
276
+ const identity = describeSyncIdentity(profileOptions);
277
+ if (identity) {
278
+ const label = identity.tenantSource === 'api-key'
279
+ ? describeSyncTenantLabel(identity)
280
+ : describeSyncTenantLabel({
281
+ tenantId: tenant?.tenantId ?? identity.tenantId,
282
+ tenantSource: 'config',
283
+ });
284
+ writeHumanProgress(context, `Deleting as ${identity.credential} → tenant ${label}`);
285
+ }
286
+ if (context.dryRun) {
287
+ context.output.writeData({
288
+ endpoint: `/api/harnesses/${idOrKey}`,
289
+ harnessKey: idOrKey,
290
+ deleted: false,
291
+ dryRun: true,
292
+ });
293
+ return { exitCode: 0 };
294
+ }
295
+ await confirmHarnessDelete(context, idOrKey);
296
+ const response = await callControlAPI(`/harnesses/${encodeURIComponent(idOrKey)}`, { method: 'DELETE' }, profileOptions);
297
+ writeHumanProgress(context, `Deleted harness ${response.harnessKey ?? idOrKey} from tenant ${response.tenantId ?? 'unknown'} ` +
298
+ `(${response.deleted?.versions ?? 0} versions, ${response.deleted?.agents ?? 0} agents, ` +
299
+ `${response.deleted?.artifacts ?? 0} artifacts, ${response.deleted?.relationships ?? 0} relationships).`);
300
+ context.output.writeData({ ...response, dryRun: false });
301
+ return { exitCode: 0 };
302
+ }
262
303
  case 'analyze': {
263
304
  await approveScanIfNeeded(scanPlan, {
264
305
  context,
@@ -494,7 +535,7 @@ export async function runHarnessCommand(context) {
494
535
  return { exitCode: 0 };
495
536
  }
496
537
  default:
497
- throw new CliInputError(`Unknown harness command '${subcommand}'.`, 'Usage: moda harness analyze|pull|validate-report|approve|scan|candidates|sync|agents|graph|explain|write-summary|status');
538
+ throw new CliInputError(`Unknown harness command '${subcommand}'.`, 'Usage: moda harness analyze|pull|validate-report|approve|scan|candidates|sync|delete|agents|graph|explain|write-summary|status');
498
539
  }
499
540
  }
500
541
  export function writeHarnessAnalyzeResult(context, payload) {
@@ -1916,6 +1957,33 @@ async function approveScanIfNeeded(scanPlan, options = {}) {
1916
1957
  throw new CliInputError(options.declinedMessage ?? 'Harness scan was not approved.', options.declinedHint ?? 'Run `moda harness scan --path=<dir>` for a narrower scan, or rerun with `--yes` after reviewing the read plan.');
1917
1958
  }
1918
1959
  }
1960
+ /**
1961
+ * Interactive confirmation gate for `harness delete`. `--yes` skips it; in
1962
+ * machine-readable modes or without a TTY the command refuses with a
1963
+ * resumable input-required error instead of prompting (same posture as the
1964
+ * broad-scan approval above) so agents/CI never hang on stdin.
1965
+ */
1966
+ async function confirmHarnessDelete(context, idOrKey) {
1967
+ if (context.yes)
1968
+ return;
1969
+ const message = `Permanently delete harness '${idOrKey}' and all of its synced versions from Moda Cloud?`;
1970
+ if (context.outputMode !== 'human' || process.stdin.isTTY !== true) {
1971
+ throw new CliInputRequiredError(message, {
1972
+ id: 'harness_delete_approval',
1973
+ question: message,
1974
+ kind: 'confirm',
1975
+ choices: [
1976
+ { value: 'yes', label: 'Delete harness', resume_flags: ['--yes'] },
1977
+ { value: 'no', label: 'Keep harness', resume_flags: [] },
1978
+ ],
1979
+ resume_command: ['moda', 'harness', 'delete', idOrKey, '--yes'],
1980
+ }, 'Re-run with `--yes` to confirm the deletion.');
1981
+ }
1982
+ const approved = await confirm({ message, default: false });
1983
+ if (!approved) {
1984
+ throw new CliInputError('Harness delete was not approved.', 'Re-run with `--yes` to skip the prompt, or leave the harness in place.');
1985
+ }
1986
+ }
1919
1987
  export function writeHumanProgress(context, message) {
1920
1988
  if (context.outputMode !== 'human')
1921
1989
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moda-ai/cli",
3
- "version": "1.19.2",
3
+ "version": "1.20.0",
4
4
  "description": "CLI for Moda - AI agent analytics and observability",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": "moda.skill_index.v1",
3
- "bundled_at": "2026-07-11T20:58:39.588Z",
4
- "cli_version": "1.19.2",
3
+ "bundled_at": "2026-07-11T21:49:32.595Z",
4
+ "cli_version": "1.20.0",
5
5
  "skills": [
6
6
  {
7
7
  "id": "integration-node-anthropic",