mrvn-cli 0.5.16 → 0.5.17

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/index.d.ts CHANGED
@@ -16,18 +16,39 @@ interface MarvinUserConfig {
16
16
  interface GitConfig {
17
17
  remote?: string;
18
18
  }
19
- interface ConditionalJiraStatusEntry {
20
- default: string[];
21
- inSprint?: string[];
19
+ /**
20
+ * Conditional status mapping: Jira status → Marvin status with sprint context.
21
+ * Used when a Jira status should map differently based on sprint membership.
22
+ */
23
+ interface ConditionalJiraStatusMapping {
24
+ default: string;
25
+ inSprint?: string;
22
26
  }
23
- interface JiraStatusMap {
24
- [marvinStatus: string]: string[] | ConditionalJiraStatusEntry;
27
+ /**
28
+ * Flat Jira→Marvin status map (spec format).
29
+ * Keys are Jira status names, values are Marvin status strings or conditional objects.
30
+ *
31
+ * Example:
32
+ * "In Progress": in-progress
33
+ * "To Do":
34
+ * default: backlog
35
+ * inSprint: ready
36
+ */
37
+ interface FlatJiraStatusMap {
38
+ [jiraStatus: string]: string | ConditionalJiraStatusMapping;
39
+ }
40
+ /**
41
+ * Legacy Marvin→Jira[] status map (nested format).
42
+ * Keys are Marvin status names, values are arrays of Jira status names.
43
+ */
44
+ interface LegacyJiraStatusMap {
45
+ [marvinStatus: string]: string[];
25
46
  }
26
47
  interface JiraProjectConfig {
27
48
  projectKey?: string;
28
- statusMap?: {
29
- action?: JiraStatusMap;
30
- task?: JiraStatusMap;
49
+ statusMap?: FlatJiraStatusMap | {
50
+ action?: LegacyJiraStatusMap;
51
+ task?: LegacyJiraStatusMap;
31
52
  };
32
53
  }
33
54
  interface MarvinProjectConfig {
package/dist/index.js CHANGED
@@ -14505,11 +14505,25 @@ import { tool as tool2 } from "@anthropic-ai/claude-agent-sdk";
14505
14505
 
14506
14506
  // src/storage/progress.ts
14507
14507
  var DONE_STATUSES = /* @__PURE__ */ new Set(["done", "closed", "resolved", "cancelled"]);
14508
+ var STATUS_PROGRESS_DEFAULTS = {
14509
+ done: 100,
14510
+ closed: 100,
14511
+ resolved: 100,
14512
+ obsolete: 100,
14513
+ "wont do": 100,
14514
+ cancelled: 100,
14515
+ review: 80,
14516
+ "in-progress": 40,
14517
+ ready: 5,
14518
+ blocked: 10,
14519
+ backlog: 0,
14520
+ open: 0
14521
+ };
14508
14522
  function getEffectiveProgress(frontmatter) {
14509
14523
  if (DONE_STATUSES.has(frontmatter.status)) return 100;
14510
14524
  const raw = frontmatter.progress;
14511
14525
  if (typeof raw === "number") return Math.max(0, Math.min(100, Math.round(raw)));
14512
- return 0;
14526
+ return STATUS_PROGRESS_DEFAULTS[frontmatter.status] ?? 0;
14513
14527
  }
14514
14528
  function propagateProgressFromTask(store, taskId) {
14515
14529
  const updated = [];
@@ -25092,35 +25106,62 @@ var DEFAULT_TASK_STATUS_MAP = {
25092
25106
  blocked: ["Blocked"],
25093
25107
  backlog: ["To Do", "Open", "Backlog", "New"]
25094
25108
  };
25095
- function isConditionalEntry(value) {
25096
- return !Array.isArray(value) && typeof value === "object" && "default" in value;
25109
+ function isLegacyFormat(statusMap) {
25110
+ if (!statusMap || typeof statusMap !== "object") return false;
25111
+ const keys = Object.keys(statusMap);
25112
+ if (!keys.every((k) => k === "action" || k === "task")) return false;
25113
+ for (const key of keys) {
25114
+ const val = statusMap[key];
25115
+ if (typeof val !== "object" || val === null) return false;
25116
+ for (const innerVal of Object.values(val)) {
25117
+ if (!Array.isArray(innerVal)) return false;
25118
+ if (!innerVal.every((v) => typeof v === "string")) return false;
25119
+ }
25120
+ }
25121
+ return true;
25097
25122
  }
25098
- function buildStatusLookup(configMap, defaults, inSprint = false) {
25099
- const map2 = configMap ?? defaults;
25123
+ function buildLegacyLookup(legacyMap) {
25100
25124
  const lookup = /* @__PURE__ */ new Map();
25101
- for (const [marvinStatus, value] of Object.entries(map2)) {
25102
- const statuses = isConditionalEntry(value) ? value.default : value;
25103
- for (const js of statuses) {
25125
+ for (const [marvinStatus, jiraStatuses] of Object.entries(legacyMap)) {
25126
+ for (const js of jiraStatuses) {
25104
25127
  lookup.set(js.toLowerCase(), marvinStatus);
25105
25128
  }
25106
25129
  }
25107
- if (inSprint) {
25108
- for (const [marvinStatus, value] of Object.entries(map2)) {
25109
- if (isConditionalEntry(value) && value.inSprint) {
25110
- for (const js of value.inSprint) {
25111
- lookup.set(js.toLowerCase(), marvinStatus);
25112
- }
25113
- }
25130
+ return lookup;
25131
+ }
25132
+ function buildFlatLookup(flatMap, inSprint) {
25133
+ const lookup = /* @__PURE__ */ new Map();
25134
+ for (const [jiraStatus, value] of Object.entries(flatMap)) {
25135
+ if (typeof value === "string") {
25136
+ lookup.set(jiraStatus.toLowerCase(), value);
25137
+ } else {
25138
+ const resolved = inSprint && value.inSprint ? value.inSprint : value.default;
25139
+ lookup.set(jiraStatus.toLowerCase(), resolved);
25114
25140
  }
25115
25141
  }
25116
25142
  return lookup;
25117
25143
  }
25118
- function mapJiraStatusForAction(status, configMap, inSprint) {
25119
- const lookup = buildStatusLookup(configMap, DEFAULT_ACTION_STATUS_MAP, inSprint ?? false);
25144
+ function normalizeStatusMap(statusMap) {
25145
+ if (!statusMap) return {};
25146
+ if (isLegacyFormat(statusMap)) {
25147
+ return { legacy: statusMap };
25148
+ }
25149
+ return { flat: statusMap };
25150
+ }
25151
+ function mapJiraStatusForAction(status, resolved, inSprint = false) {
25152
+ if (resolved.flat) {
25153
+ const lookup2 = buildFlatLookup(resolved.flat, inSprint);
25154
+ return lookup2.get(status.toLowerCase()) ?? "open";
25155
+ }
25156
+ const lookup = buildLegacyLookup(resolved.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP);
25120
25157
  return lookup.get(status.toLowerCase()) ?? "open";
25121
25158
  }
25122
- function mapJiraStatusForTask(status, configMap, inSprint) {
25123
- const lookup = buildStatusLookup(configMap, DEFAULT_TASK_STATUS_MAP, inSprint ?? false);
25159
+ function mapJiraStatusForTask(status, resolved, inSprint = false) {
25160
+ if (resolved.flat) {
25161
+ const lookup2 = buildFlatLookup(resolved.flat, inSprint);
25162
+ return lookup2.get(status.toLowerCase()) ?? "backlog";
25163
+ }
25164
+ const lookup = buildLegacyLookup(resolved.legacy?.task ?? DEFAULT_TASK_STATUS_MAP);
25124
25165
  return lookup.get(status.toLowerCase()) ?? "backlog";
25125
25166
  }
25126
25167
  function isInActiveSprint(store, tags) {
@@ -25181,7 +25222,8 @@ async function fetchJiraStatus(store, client, host, artifactId, statusMap) {
25181
25222
  try {
25182
25223
  const issue2 = await client.getIssueWithLinks(jiraKey);
25183
25224
  const inSprint = isInActiveSprint(store, doc.frontmatter.tags);
25184
- const proposedStatus = artifactType === "task" ? mapJiraStatusForTask(issue2.fields.status.name, statusMap?.task, inSprint) : mapJiraStatusForAction(issue2.fields.status.name, statusMap?.action, inSprint);
25225
+ const resolved = statusMap ?? {};
25226
+ const proposedStatus = artifactType === "task" ? mapJiraStatusForTask(issue2.fields.status.name, resolved, inSprint) : mapJiraStatusForAction(issue2.fields.status.name, resolved, inSprint);
25185
25227
  const currentStatus = doc.frontmatter.status;
25186
25228
  const linkedIssues = [];
25187
25229
  if (issue2.fields.subtasks) {
@@ -25639,7 +25681,8 @@ async function processIssue(issue2, client, host, dateRange, jiraKeyToArtifacts,
25639
25681
  const jiraStatus = issue2.fields.status?.name;
25640
25682
  if (jiraStatus) {
25641
25683
  const inSprint = store ? isInActiveSprint(store, fm.tags) : false;
25642
- proposedStatus = artifactType === "task" ? mapJiraStatusForTask(jiraStatus, statusMap?.task, inSprint) : mapJiraStatusForAction(jiraStatus, statusMap?.action, inSprint);
25684
+ const resolved = statusMap ?? {};
25685
+ proposedStatus = artifactType === "task" ? mapJiraStatusForTask(jiraStatus, resolved, inSprint) : mapJiraStatusForAction(jiraStatus, resolved, inSprint);
25643
25686
  }
25644
25687
  }
25645
25688
  marvinArtifacts.push({
@@ -25768,20 +25811,6 @@ var COMPLEXITY_WEIGHTS = {
25768
25811
  "very-complex": 8
25769
25812
  };
25770
25813
  var DEFAULT_WEIGHT = 3;
25771
- var STATUS_PROGRESS_DEFAULTS = {
25772
- done: 100,
25773
- closed: 100,
25774
- resolved: 100,
25775
- obsolete: 100,
25776
- "wont do": 100,
25777
- cancelled: 100,
25778
- review: 80,
25779
- "in-progress": 40,
25780
- ready: 5,
25781
- backlog: 0,
25782
- open: 0
25783
- };
25784
- var BLOCKED_DEFAULT_PROGRESS = 10;
25785
25814
  function resolveWeight(complexity) {
25786
25815
  if (complexity && complexity in COMPLEXITY_WEIGHTS) {
25787
25816
  return { weight: COMPLEXITY_WEIGHTS[complexity], weightSource: "complexity" };
@@ -25797,9 +25826,6 @@ function resolveProgress(frontmatter, commentAnalysisProgress) {
25797
25826
  return { progress: Math.max(0, Math.min(100, Math.round(commentAnalysisProgress))), progressSource: "comment-analysis" };
25798
25827
  }
25799
25828
  const status = frontmatter.status;
25800
- if (status === "blocked") {
25801
- return { progress: BLOCKED_DEFAULT_PROGRESS, progressSource: "status-default" };
25802
- }
25803
25829
  const defaultProgress = STATUS_PROGRESS_DEFAULTS[status] ?? 0;
25804
25830
  return { progress: defaultProgress, progressSource: "status-default" };
25805
25831
  }
@@ -25892,7 +25918,8 @@ async function assessSprintProgress(store, client, host, options = {}) {
25892
25918
  if (jiraData) {
25893
25919
  jiraStatus = jiraData.issue.fields.status.name;
25894
25920
  const inSprint = isInActiveSprint(store, fm.tags);
25895
- proposedMarvinStatus = fm.type === "task" ? mapJiraStatusForTask(jiraStatus, options.statusMap?.task, inSprint) : mapJiraStatusForAction(jiraStatus, options.statusMap?.action, inSprint);
25921
+ const resolved = options.statusMap ?? {};
25922
+ proposedMarvinStatus = fm.type === "task" ? mapJiraStatusForTask(jiraStatus, resolved, inSprint) : mapJiraStatusForAction(jiraStatus, resolved, inSprint);
25896
25923
  const subtasks = jiraData.issue.fields.subtasks ?? [];
25897
25924
  if (subtasks.length > 0) {
25898
25925
  jiraSubtaskProgress = computeSubtaskProgress(subtasks);
@@ -25923,6 +25950,20 @@ async function assessSprintProgress(store, client, host, options = {}) {
25923
25950
  proposedValue: jiraSubtaskProgress,
25924
25951
  reason: `Jira ${jiraKey} subtask progress is ${jiraSubtaskProgress}%`
25925
25952
  });
25953
+ } else if (statusDrift && proposedMarvinStatus && !fm.progressOverride) {
25954
+ const hasExplicitProgress = "progress" in fm && typeof fm.progress === "number";
25955
+ if (!hasExplicitProgress) {
25956
+ const proposedProgress = STATUS_PROGRESS_DEFAULTS[proposedMarvinStatus] ?? 0;
25957
+ if (proposedProgress !== currentProgress) {
25958
+ proposedUpdates.push({
25959
+ artifactId: fm.id,
25960
+ field: "progress",
25961
+ currentValue: currentProgress,
25962
+ proposedValue: proposedProgress,
25963
+ reason: `Status changing to "${proposedMarvinStatus}" \u2192 default progress ${proposedProgress}%`
25964
+ });
25965
+ }
25966
+ }
25926
25967
  }
25927
25968
  const tags = fm.tags ?? [];
25928
25969
  const focusTag = tags.find((t) => t.startsWith("focus:"));
@@ -26301,7 +26342,7 @@ function findByJiraKey(store, jiraKey) {
26301
26342
  function createJiraTools(store, projectConfig) {
26302
26343
  const jiraUserConfig = loadUserConfig().jira;
26303
26344
  const defaultProjectKey = projectConfig?.jira?.projectKey;
26304
- const statusMap = projectConfig?.jira?.statusMap;
26345
+ const statusMap = normalizeStatusMap(projectConfig?.jira?.statusMap);
26305
26346
  return [
26306
26347
  // --- Local read tools ---
26307
26348
  tool20(
@@ -26928,22 +26969,31 @@ function createJiraTools(store, projectConfig) {
26928
26969
  const s = issue2.fields.status.name;
26929
26970
  statusCounts.set(s, (statusCounts.get(s) ?? 0) + 1);
26930
26971
  }
26931
- const actionMap = statusMap?.action ?? DEFAULT_ACTION_STATUS_MAP;
26932
- const taskMap = statusMap?.task ?? DEFAULT_TASK_STATUS_MAP;
26933
26972
  const actionLookup = /* @__PURE__ */ new Map();
26934
- for (const [marvin, value] of Object.entries(actionMap)) {
26935
- const jiraStatuses = Array.isArray(value) ? value : value.default;
26936
- for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
26937
- if (!Array.isArray(value) && value.inSprint) {
26938
- for (const js of value.inSprint) actionLookup.set(js.toLowerCase(), `${marvin} (inSprint)`);
26939
- }
26940
- }
26941
26973
  const taskLookup = /* @__PURE__ */ new Map();
26942
- for (const [marvin, value] of Object.entries(taskMap)) {
26943
- const jiraStatuses = Array.isArray(value) ? value : value.default;
26944
- for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
26945
- if (!Array.isArray(value) && value.inSprint) {
26946
- for (const js of value.inSprint) taskLookup.set(js.toLowerCase(), `${marvin} (inSprint)`);
26974
+ if (statusMap.flat) {
26975
+ for (const [jiraStatus, value] of Object.entries(statusMap.flat)) {
26976
+ const lower = jiraStatus.toLowerCase();
26977
+ if (typeof value === "string") {
26978
+ actionLookup.set(lower, value);
26979
+ taskLookup.set(lower, value);
26980
+ } else {
26981
+ actionLookup.set(lower, value.default);
26982
+ taskLookup.set(lower, value.default);
26983
+ if (value.inSprint) {
26984
+ actionLookup.set(lower, `${value.default} / ${value.inSprint} (inSprint)`);
26985
+ taskLookup.set(lower, `${value.default} / ${value.inSprint} (inSprint)`);
26986
+ }
26987
+ }
26988
+ }
26989
+ } else {
26990
+ const actionMap = statusMap.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP;
26991
+ const taskMap = statusMap.legacy?.task ?? DEFAULT_TASK_STATUS_MAP;
26992
+ for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
26993
+ for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
26994
+ }
26995
+ for (const [marvin, jiraStatuses] of Object.entries(taskMap)) {
26996
+ for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
26947
26997
  }
26948
26998
  }
26949
26999
  const parts = [
@@ -26965,25 +27015,20 @@ function createJiraTools(store, projectConfig) {
26965
27015
  if (!taskTarget) unmappedTask.push(status);
26966
27016
  }
26967
27017
  if (unmappedAction.length > 0 || unmappedTask.length > 0) {
27018
+ const allUnmapped = [.../* @__PURE__ */ new Set([...unmappedAction, ...unmappedTask])];
26968
27019
  parts.push("");
26969
27020
  parts.push("To fix unmapped statuses, add jira.statusMap to .marvin/config.yaml:");
26970
27021
  parts.push(" jira:");
26971
27022
  parts.push(" statusMap:");
26972
- if (unmappedAction.length > 0) {
26973
- parts.push(" action:");
26974
- parts.push(` # Map these: ${unmappedAction.join(", ")}`);
26975
- parts.push(" # <marvin-status>: [<jira-status>, ...]");
26976
- }
26977
- if (unmappedTask.length > 0) {
26978
- parts.push(" task:");
26979
- parts.push(` # Map these: ${unmappedTask.join(", ")}`);
26980
- parts.push(" # <marvin-status>: [<jira-status>, ...]");
27023
+ for (const s of allUnmapped) {
27024
+ parts.push(` "${s}": <marvin-status>`);
26981
27025
  }
27026
+ parts.push(" # Supported marvin statuses: done, in-progress, review, ready, blocked, backlog, open");
26982
27027
  } else {
26983
27028
  parts.push("");
26984
27029
  parts.push("All statuses are mapped.");
26985
27030
  }
26986
- const usingConfig = statusMap?.action || statusMap?.task;
27031
+ const usingConfig = statusMap.flat || statusMap.legacy;
26987
27032
  parts.push("");
26988
27033
  parts.push(usingConfig ? "Using status maps from .marvin/config.yaml." : "Using built-in default status maps (no jira.statusMap in config).");
26989
27034
  return {
@@ -32372,7 +32417,7 @@ async function jiraSyncCommand(artifactId, options = {}) {
32372
32417
  );
32373
32418
  return;
32374
32419
  }
32375
- const statusMap = project.config.jira?.statusMap;
32420
+ const statusMap = normalizeStatusMap(project.config.jira?.statusMap);
32376
32421
  const label = artifactId ? `Checking ${artifactId} against Jira...` : "Checking all Jira-linked actions/tasks...";
32377
32422
  console.log(chalk20.dim(label));
32378
32423
  if (options.dryRun) {
@@ -32489,9 +32534,7 @@ async function jiraStatusesCommand(projectKey) {
32489
32534
  return;
32490
32535
  }
32491
32536
  console.log(chalk20.dim(`Fetching statuses from Jira project ${resolvedProjectKey}...`));
32492
- const statusMap = project.config.jira?.statusMap;
32493
- const actionMap = statusMap?.action ?? DEFAULT_ACTION_STATUS_MAP;
32494
- const taskMap = statusMap?.task ?? DEFAULT_TASK_STATUS_MAP;
32537
+ const statusMap = normalizeStatusMap(project.config.jira?.statusMap);
32495
32538
  const email3 = jiraUserConfig?.email ?? process.env.JIRA_EMAIL;
32496
32539
  const apiToken = jiraUserConfig?.apiToken ?? process.env.JIRA_API_TOKEN;
32497
32540
  const auth = "Basic " + Buffer.from(`${email3}:${apiToken}`).toString("base64");
@@ -32515,14 +32558,28 @@ async function jiraStatusesCommand(projectKey) {
32515
32558
  statusCounts.set(s, (statusCounts.get(s) ?? 0) + 1);
32516
32559
  }
32517
32560
  const actionLookup = /* @__PURE__ */ new Map();
32518
- for (const [marvin, value] of Object.entries(actionMap)) {
32519
- const jiraStatuses = Array.isArray(value) ? value : value.default;
32520
- for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
32521
- }
32522
32561
  const taskLookup = /* @__PURE__ */ new Map();
32523
- for (const [marvin, value] of Object.entries(taskMap)) {
32524
- const jiraStatuses = Array.isArray(value) ? value : value.default;
32525
- for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
32562
+ if (statusMap.flat) {
32563
+ for (const [jiraStatus, value] of Object.entries(statusMap.flat)) {
32564
+ const lower = jiraStatus.toLowerCase();
32565
+ if (typeof value === "string") {
32566
+ actionLookup.set(lower, value);
32567
+ taskLookup.set(lower, value);
32568
+ } else {
32569
+ const label = value.inSprint ? `${value.default} / ${value.inSprint} (inSprint)` : value.default;
32570
+ actionLookup.set(lower, label);
32571
+ taskLookup.set(lower, label);
32572
+ }
32573
+ }
32574
+ } else {
32575
+ const actionMap = statusMap.legacy?.action ?? DEFAULT_ACTION_STATUS_MAP;
32576
+ const taskMap = statusMap.legacy?.task ?? DEFAULT_TASK_STATUS_MAP;
32577
+ for (const [marvin, jiraStatuses] of Object.entries(actionMap)) {
32578
+ for (const js of jiraStatuses) actionLookup.set(js.toLowerCase(), marvin);
32579
+ }
32580
+ for (const [marvin, jiraStatuses] of Object.entries(taskMap)) {
32581
+ for (const js of jiraStatuses) taskLookup.set(js.toLowerCase(), marvin);
32582
+ }
32526
32583
  }
32527
32584
  console.log(
32528
32585
  `
@@ -32545,14 +32602,11 @@ Found ${chalk20.bold(String(statusCounts.size))} distinct statuses in ${chalk20.
32545
32602
  console.log(chalk20.yellow("\nSome statuses are unmapped. Add jira.statusMap to .marvin/config.yaml:"));
32546
32603
  console.log(chalk20.dim(" jira:"));
32547
32604
  console.log(chalk20.dim(" statusMap:"));
32548
- console.log(chalk20.dim(" action:"));
32549
- console.log(chalk20.dim(' <marvin-status>: ["<Jira Status>", ...]'));
32550
- console.log(chalk20.dim(" task:"));
32551
- console.log(chalk20.dim(' <marvin-status>: ["<Jira Status>", ...]'));
32605
+ console.log(chalk20.dim(' "<Jira Status>": <marvin-status>'));
32552
32606
  } else {
32553
32607
  console.log(chalk20.green("\nAll statuses are mapped."));
32554
32608
  }
32555
- const usingConfig = statusMap?.action || statusMap?.task;
32609
+ const usingConfig = statusMap.flat || statusMap.legacy;
32556
32610
  console.log(
32557
32611
  chalk20.dim(
32558
32612
  usingConfig ? "\nUsing status maps from .marvin/config.yaml." : "\nUsing built-in default status maps (no jira.statusMap in config)."
@@ -32587,7 +32641,7 @@ async function jiraDailyCommand(options) {
32587
32641
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
32588
32642
  const fromDate = options.from ?? today;
32589
32643
  const toDate = options.to ?? fromDate;
32590
- const statusMap = proj.config.jira?.statusMap;
32644
+ const statusMap = normalizeStatusMap(proj.config.jira?.statusMap);
32591
32645
  const rangeLabel = fromDate === toDate ? fromDate : `${fromDate} to ${toDate}`;
32592
32646
  console.log(
32593
32647
  chalk20.dim(`Fetching Jira daily summary for ${resolvedProjectKey} \u2014 ${rangeLabel}...`)
@@ -32704,7 +32758,7 @@ function createProgram() {
32704
32758
  const program = new Command();
32705
32759
  program.name("marvin").description(
32706
32760
  "AI-powered product development assistant with Product Owner, Delivery Manager, and Technical Lead personas"
32707
- ).version("0.5.16");
32761
+ ).version("0.5.17");
32708
32762
  program.command("init").description("Initialize a new Marvin project in the current directory").action(async () => {
32709
32763
  await initCommand();
32710
32764
  });