minutes-mcp 0.25.2 → 0.25.4

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.
@@ -5,6 +5,7 @@ import { lstat, mkdir, open, opendir, realpath, stat } from "node:fs/promises";
5
5
  import { basename, dirname, extname, isAbsolute, join, relative } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { decodePolicyUtf8, fingerprintTextFileFromBoundParent, readTextFileWithRevisionFromBoundParent, writeOperatorDiagnostic, } from "./secure-read.js";
8
+ import { nodeChildEnvironment } from "./node-child.js";
8
9
  const MAX_AUTHORIZATION_ATTEMPTS = 2;
9
10
  const DEFAULT_FENCE_TIMEOUT_MS = 5_000;
10
11
  const DEFAULT_AUTHORIZATION_TIMEOUT_MS = 15_000;
@@ -1431,7 +1432,7 @@ async function dispatchStableCorpusLease(root, operation, hooks = {}) {
1431
1432
  detached: process.platform !== "win32",
1432
1433
  stdio: ["pipe", "pipe", "pipe"],
1433
1434
  windowsHide: true,
1434
- env: { ...process.env },
1435
+ env: nodeChildEnvironment(),
1435
1436
  });
1436
1437
  child.stderr?.resume();
1437
1438
  let resolveTermination;
package/dist/index.d.ts CHANGED
@@ -315,6 +315,30 @@ export declare function liveMeetingSnippet(body: string, query?: string): string
315
315
  export declare function shouldRunMainEntry(argv1: string | null | undefined, moduleFilename: string): boolean;
316
316
  export declare function parseKnowledgeConfig(configContent: string): KnowledgeConfigStatus | null;
317
317
  export declare function createCapabilityRepairCoordinator(repair: () => Promise<boolean>, maximumAttempts?: number): () => Promise<boolean>;
318
+ export type HealthItem = {
319
+ label?: unknown;
320
+ state?: unknown;
321
+ [key: string]: unknown;
322
+ };
323
+ export type HealthOutput = {
324
+ ok: boolean;
325
+ items: HealthItem[] | null;
326
+ engine?: string;
327
+ effectiveEngine?: string;
328
+ model?: string;
329
+ };
330
+ export declare function parseHealthOutput(stdout: string): HealthOutput;
331
+ type WhisperModelCheckState = {
332
+ done: boolean;
333
+ };
334
+ export type EnsureWhisperModelOptions = {
335
+ checkState?: WhisperModelCheckState;
336
+ health?: () => Promise<string>;
337
+ configFileExists?: () => Promise<boolean>;
338
+ setup?: (model: string) => Promise<void>;
339
+ log?: (message: string) => void;
340
+ };
341
+ export declare function ensureWhisperModel(options?: EnsureWhisperModelOptions): Promise<void>;
318
342
  declare function isCliAvailable(): Promise<boolean>;
319
343
  export declare function repairCliCapabilities(required: readonly string[], capabilities: CapabilityProbeResult, repair: () => Promise<boolean>, reprobe: () => CapabilityProbeResult): Promise<CapabilityProbeResult>;
320
344
  export declare function mcpCliChildEnv(extra?: Record<string, string>): Record<string, string | undefined>;
@@ -451,7 +475,8 @@ export type AgentTrustReadiness = {
451
475
  qmd_retirement: "ready-clean" | "blocked";
452
476
  remediation?: string;
453
477
  };
454
- export declare function readAgentTrustReadiness(runner?: MinutesRunner): Promise<AgentTrustReadiness>;
478
+ export declare function cliTooOldGuidance(installedVersion: string, minimumVersion?: string, platform?: NodeJS.Platform): string;
479
+ export declare function readAgentTrustReadiness(runner?: MinutesRunner, platform?: NodeJS.Platform): Promise<AgentTrustReadiness>;
455
480
  export declare function requireAgentTrustReadiness(runner?: MinutesRunner): Promise<AgentTrustReadiness>;
456
481
  export declare function afterAgentTrustReadiness<T>(operation: () => Promise<T>, runner?: MinutesRunner): Promise<T>;
457
482
  export declare function afterRequiredCli<T>(operation: () => Promise<T>, cliProbe?: () => Promise<boolean>): Promise<T>;
package/dist/index.js CHANGED
@@ -44,14 +44,15 @@ import { execFile, spawn, spawnSync } from "child_process";
44
44
  import { createHash } from "crypto";
45
45
  import { promisify } from "util";
46
46
  import { closeSync, constants, copyFileSync, existsSync, fstatSync, mkdirSync, openSync, readdirSync, realpathSync, statSync, } from "fs";
47
- import { mkdir, readFile, rm, stat, writeFile } from "fs/promises";
47
+ import { access, mkdir, readFile, rm, stat, writeFile } from "fs/promises";
48
48
  import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "path";
49
49
  import { fileURLToPath } from "url";
50
50
  import { homedir } from "os";
51
51
  import { parse as parseYaml } from "yaml";
52
52
  import * as reader from "minutes-sdk";
53
53
  import { canonicalizeRoot, expandHomeLikePath, readTextFileInDirectory, validatePathInDirectories, validatePathInDirectory, } from "./paths.js";
54
- import { isCliCompatible } from "./version.js";
54
+ import { isCliCompatible, parseVersion } from "./version.js";
55
+ import { nodeChildEnvironment } from "./node-child.js";
55
56
  import { hasFeature, probeCapabilitiesSync, } from "./capabilities.js";
56
57
  import { downloadReleaseBinaryWithChecksum, extractZipWithPowerShell, } from "./autoInstall.js";
57
58
  import { attachCaptureRelay, } from "./captureRelay.js";
@@ -1569,7 +1570,7 @@ const COPILOT_SUPPORTED = hasFeature(CLI_CAPABILITIES, "copilot_realtime");
1569
1570
  // `./version.ts` (see issue #183). Hosted `.mcpb` bundles will run
1570
1571
  // against CLIs with different minor/patch numbers within the same
1571
1572
  // major; that is explicitly supported.
1572
- const MCP_SERVER_VERSION = "0.25.2";
1573
+ const MCP_SERVER_VERSION = "0.25.4";
1573
1574
  export function parseKnowledgeConfig(configContent) {
1574
1575
  const knowledgeMatch = configContent.match(/\[knowledge\][\s\S]*?(?=\n\[|$)/);
1575
1576
  if (!knowledgeMatch) {
@@ -1776,38 +1777,150 @@ async function checkCliVersion() {
1776
1777
  // Version check is best-effort. Don't block on failure.
1777
1778
  }
1778
1779
  }
1779
- // ── Auto-setup: download whisper model if missing ───────────
1780
- // Recording needs a whisper model (~75MB for tiny). If the CLI is
1781
- // available but the model isn't downloaded, trigger setup automatically
1782
- // in the background so the first "start recording" just works.
1780
+ // ── Auto-setup: download configured whisper model if missing ───────────
1781
+ // Whisper recording needs a model. If Whisper is configured but its model
1782
+ // isn't downloaded, trigger setup automatically in the background so the
1783
+ // first "start recording" just works without changing the user's choice.
1783
1784
  let modelCheckDone = false;
1784
- async function ensureWhisperModel() {
1785
- if (modelCheckDone)
1785
+ function isHealthItem(value) {
1786
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1787
+ }
1788
+ export function parseHealthOutput(stdout) {
1789
+ let parsed;
1790
+ try {
1791
+ parsed = JSON.parse(stdout);
1792
+ }
1793
+ catch {
1794
+ return { ok: false, items: null };
1795
+ }
1796
+ if (Array.isArray(parsed)) {
1797
+ return parsed.every(isHealthItem)
1798
+ ? { ok: true, items: parsed }
1799
+ : { ok: false, items: null };
1800
+ }
1801
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
1802
+ return { ok: false, items: null };
1803
+ }
1804
+ const envelope = parsed;
1805
+ if (envelope.ok !== true) {
1806
+ return { ok: false, items: null };
1807
+ }
1808
+ const data = envelope.data;
1809
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
1810
+ return { ok: false, items: null };
1811
+ }
1812
+ const dataRecord = data;
1813
+ const items = dataRecord.items;
1814
+ if (!Array.isArray(items) || !items.every(isHealthItem)) {
1815
+ return { ok: false, items: null };
1816
+ }
1817
+ return {
1818
+ ok: true,
1819
+ items,
1820
+ ...(typeof dataRecord.engine === "string" ? { engine: dataRecord.engine } : {}),
1821
+ ...(typeof dataRecord.effective_engine === "string"
1822
+ ? { effectiveEngine: dataRecord.effective_engine }
1823
+ : {}),
1824
+ ...(typeof dataRecord.model === "string" ? { model: dataRecord.model } : {}),
1825
+ };
1826
+ }
1827
+ async function configFileExists() {
1828
+ const configRoot = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
1829
+ try {
1830
+ await access(join(configRoot, "minutes", "config.toml"));
1831
+ return true;
1832
+ }
1833
+ catch (error) {
1834
+ if (error.code === "ENOENT")
1835
+ return false;
1836
+ throw error;
1837
+ }
1838
+ }
1839
+ export async function ensureWhisperModel(options = {}) {
1840
+ const checkState = options.checkState;
1841
+ if (checkState ? checkState.done : modelCheckDone)
1786
1842
  return;
1787
- modelCheckDone = true;
1843
+ if (checkState)
1844
+ checkState.done = true;
1845
+ else
1846
+ modelCheckDone = true;
1847
+ const log = options.log ?? ((message) => console.error(message));
1848
+ const health = options.health ?? (async () => {
1849
+ const { stdout } = await execFileAsync(MINUTES_BIN, ["health", "--json"], {
1850
+ timeout: 10000,
1851
+ env: mcpCliChildEnv(),
1852
+ });
1853
+ return stdout;
1854
+ });
1855
+ const setup = options.setup ?? (async (model) => {
1856
+ await execFileAsync(MINUTES_BIN, ["setup", "--model", model], {
1857
+ timeout: 300000,
1858
+ env: mcpCliChildEnv(),
1859
+ });
1860
+ });
1861
+ const doesConfigFileExist = options.configFileExists ?? configFileExists;
1862
+ let healthOutput;
1788
1863
  try {
1789
- // health --json returns an array of { label, state, detail, optional } items.
1790
- // The "Speech model" item has state "ready" when downloaded.
1791
- const { stdout } = await execFileAsync(MINUTES_BIN, ["health", "--json"], { timeout: 10000, env: mcpCliChildEnv() });
1792
- const items = JSON.parse(stdout);
1793
- const modelItem = Array.isArray(items) && items.find((i) => i.label === "Speech model");
1794
- if (modelItem && modelItem.state === "ready") {
1795
- console.error("[Minutes] Whisper model ready");
1864
+ healthOutput = parseHealthOutput(await health());
1865
+ }
1866
+ catch (error) {
1867
+ const message = error instanceof Error ? error.message : String(error);
1868
+ log(`[Minutes] Unable to check speech model status — skipping Whisper auto-setup: ${message}`);
1869
+ return;
1870
+ }
1871
+ if (!healthOutput.ok || healthOutput.items === null) {
1872
+ log("[Minutes] Unrecognized health --json output — skipping Whisper auto-setup");
1873
+ return;
1874
+ }
1875
+ const modelItem = healthOutput.items.find((item) => item.label === "Speech model");
1876
+ if (!modelItem) {
1877
+ log("[Minutes] unrecognized health items — skipping Whisper auto-setup");
1878
+ return;
1879
+ }
1880
+ if (modelItem.state === "ready") {
1881
+ log("[Minutes] Whisper model ready");
1882
+ return;
1883
+ }
1884
+ if (modelItem.state !== "attention") {
1885
+ log(`[Minutes] Speech model health state is ${String(modelItem.state)} — skipping Whisper auto-setup`);
1886
+ return;
1887
+ }
1888
+ const engineForDecision = healthOutput.effectiveEngine ?? healthOutput.engine;
1889
+ if (engineForDecision !== undefined && engineForDecision.toLowerCase() !== "whisper") {
1890
+ const upgradeGuidance = healthOutput.effectiveEngine === undefined
1891
+ ? "; upgrade the CLI to let auto-setup resolve the effective engine"
1892
+ : "";
1893
+ log(`[Minutes] Transcription engine is ${engineForDecision} — skipping Whisper auto-setup${upgradeGuidance}`);
1894
+ return;
1895
+ }
1896
+ let model = healthOutput.model;
1897
+ if (model !== undefined) {
1898
+ log(`[Minutes] Whisper model ${model} is configured but not ready — downloading it...`);
1899
+ }
1900
+ else {
1901
+ let configExists;
1902
+ try {
1903
+ configExists = await doesConfigFileExist();
1904
+ }
1905
+ catch (error) {
1906
+ const message = error instanceof Error ? error.message : String(error);
1907
+ log(`[Minutes] Unable to check for an existing config — skipping Whisper auto-setup: ${message}`);
1796
1908
  return;
1797
1909
  }
1910
+ if (configExists) {
1911
+ log("[Minutes] config exists but this CLI does not report the configured model; run `minutes setup --model <your model>` or upgrade the CLI");
1912
+ return;
1913
+ }
1914
+ model = "tiny";
1915
+ log("[Minutes] No Whisper model is configured — downloading tiny model (~75MB)...");
1798
1916
  }
1799
- catch {
1800
- // health command may not exist in older CLI versions — fall through to setup
1801
- }
1802
- // Model not found — download tiny model in background
1803
- console.error("[Minutes] Whisper model not found — downloading tiny model (~75MB)...");
1804
1917
  try {
1805
- await execFileAsync(MINUTES_BIN, ["setup", "--model", "tiny"], { timeout: 300000, env: mcpCliChildEnv() });
1806
- console.error("[Minutes] ✓ Whisper tiny model downloaded — recording is ready");
1918
+ await setup(model);
1919
+ log(`[Minutes] ✓ Whisper ${model} model downloaded — recording is ready`);
1807
1920
  }
1808
- catch (e) {
1809
- console.error(`[Minutes] Model download failed: ${e.message || e}. ` +
1810
- `Run manually: minutes setup --model tiny`);
1921
+ catch (error) {
1922
+ const message = error instanceof Error ? error.message : String(error);
1923
+ log(`[Minutes] Model download failed: ${message}. Run manually: minutes setup --model ${model}`);
1811
1924
  }
1812
1925
  }
1813
1926
  // ── CLI availability detection ──────────────────────────────
@@ -2483,7 +2596,47 @@ export async function readKnowledgeStatusSnapshot(runner = runMinutes) {
2483
2596
  }
2484
2597
  return result;
2485
2598
  }
2486
- export async function readAgentTrustReadiness(runner = runMinutes) {
2599
+ const AGENT_READINESS_MIN_CLI_VERSION = "0.25.0";
2600
+ function isOlderVersion(installed, minimum) {
2601
+ const found = parseVersion(installed);
2602
+ const required = parseVersion(minimum);
2603
+ if (!found || !required)
2604
+ return false;
2605
+ if (found.major !== required.major)
2606
+ return found.major < required.major;
2607
+ if (found.minor !== required.minor)
2608
+ return found.minor < required.minor;
2609
+ return found.patch < required.patch;
2610
+ }
2611
+ function rejectedUnknownSubcommand(error, subcommand) {
2612
+ const diagnostic = [error?.stderr, error?.message]
2613
+ .filter((value) => typeof value === "string")
2614
+ .join("\n")
2615
+ .toLowerCase();
2616
+ return (diagnostic.includes(subcommand.toLowerCase()) &&
2617
+ /(?:unrecognized|unknown|invalid) subcommand/.test(diagnostic));
2618
+ }
2619
+ export function cliTooOldGuidance(installedVersion, minimumVersion = AGENT_READINESS_MIN_CLI_VERSION, platform = process.platform) {
2620
+ const update = platform === "darwin" || platform === "win32"
2621
+ ? "Install the latest free Minutes desktop app from https://useminutes.app"
2622
+ : "Update the Minutes CLI from https://useminutes.app, or run `cargo install minutes-cli`";
2623
+ return (`The Minutes engine is too old for this extension (found v${installedVersion}; ` +
2624
+ `needs v${minimumVersion} or newer). ${update}, then restart this app.`);
2625
+ }
2626
+ async function oldCliReadinessGuidance(runner, platform) {
2627
+ try {
2628
+ const { stdout } = await runner(["--version"], 5000);
2629
+ const match = stdout.match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/);
2630
+ if (!match || !isOlderVersion(match[0], AGENT_READINESS_MIN_CLI_VERSION)) {
2631
+ return null;
2632
+ }
2633
+ return cliTooOldGuidance(match[0], AGENT_READINESS_MIN_CLI_VERSION, platform);
2634
+ }
2635
+ catch {
2636
+ return null;
2637
+ }
2638
+ }
2639
+ export async function readAgentTrustReadiness(runner = runMinutes, platform = process.platform) {
2487
2640
  let stdout;
2488
2641
  try {
2489
2642
  ({ stdout } = await runner(["agent-readiness", "--json"]));
@@ -2496,6 +2649,11 @@ export async function readAgentTrustReadiness(runner = runMinutes) {
2496
2649
  // "could not be verified safely" and nothing to do about it (#774).
2497
2650
  if (error?.cliMissing)
2498
2651
  throw error;
2652
+ if (rejectedUnknownSubcommand(error, "agent-readiness")) {
2653
+ const guidance = await oldCliReadinessGuidance(runner, platform);
2654
+ if (guidance)
2655
+ throw new Error(guidance);
2656
+ }
2499
2657
  throw new Error("Minutes agent readiness could not be verified safely.");
2500
2658
  }
2501
2659
  let result;
@@ -4854,7 +5012,7 @@ export async function runIsolatedMcpProcessAudio(filePath, allowedDirs, audioExt
4854
5012
  child = spawn(helper.binary, helper.args, {
4855
5013
  detached: true,
4856
5014
  stdio: ["pipe", "pipe", "pipe", "pipe"],
4857
- env: mcpCliChildEnv(),
5015
+ env: nodeChildEnvironment(mcpCliChildEnv()),
4858
5016
  });
4859
5017
  }
4860
5018
  catch {
@@ -5657,7 +5815,7 @@ registerTool("stop_dictation", "Stop the current dictation session.", {}, { titl
5657
5815
  };
5658
5816
  });
5659
5817
  // ── Tool: list_voices ────────────────────────────────────────
5660
- registerTool("list_voices", "List enrolled voice profiles for speaker identification. Shows who has been enrolled, sample count, and model version.", {}, { title: "Voice Profiles", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async () => {
5818
+ registerTool("list_voices", "List active voice enrollments for speaker identification. Shows who has been enrolled, sample count, and model version.", {}, { title: "Voice Profiles", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async () => {
5661
5819
  if (!(await isCliAvailable())) {
5662
5820
  return { content: [{ type: "text", text: "Minutes CLI not available." }] };
5663
5821
  }
@@ -5665,7 +5823,7 @@ registerTool("list_voices", "List enrolled voice profiles for speaker identifica
5665
5823
  const profiles = parseJsonOutput(stdout);
5666
5824
  if (!Array.isArray(profiles) || profiles.length === 0) {
5667
5825
  return {
5668
- content: [{ type: "text", text: "No voice profiles enrolled. The user can enroll with: minutes enroll" }],
5826
+ content: [{ type: "text", text: "No voice profiles enrolled. The user can enroll with: minutes voice enroll" }],
5669
5827
  };
5670
5828
  }
5671
5829
  const lines = profiles.map((p) => `${p.name} — ${p.sample_count} samples, ${p.source} (${p.model_version})`);
@@ -6714,15 +6872,16 @@ registerTool("start_live_transcript", "Start real-time transcription. If a recor
6714
6872
  };
6715
6873
  });
6716
6874
  // ── Tool: read_live_transcript ──────────────────────────────
6717
- registerTool("read_live_transcript", "Read the live transcript — works during both recordings and live transcript sessions. Use 'since' to get new lines after a cursor (line number) or time window (e.g., '5m', '30s'). Use 'status' mode to check if a session is active.", {
6875
+ registerTool("read_live_transcript", "Read the live transcript — works during both recordings and live transcript sessions. Use 'since' to get new finalized lines after a cursor (line number) or time window (e.g., '5m', '30s'). Set include_current for one fresh, clearly provisional current-speech draft. Use 'status' mode to check if a session is active.", {
6718
6876
  since: z.string().optional().describe("Line number (e.g., '42') or duration (e.g., '5m', '30s'). Omit to get all lines."),
6719
6877
  status_only: z.boolean().optional().default(false).describe("If true, return session status instead of transcript lines"),
6878
+ include_current: z.boolean().optional().default(false).describe("If true, return finalized lines plus at most one fresh provisional current-speech draft and its replacement identity"),
6720
6879
  relay_cursor: z.object({
6721
6880
  session_id: z.string().optional(),
6722
6881
  transcript_seq: z.number().int().nonnegative(),
6723
6882
  nudge_seq: z.number().int().nonnegative(),
6724
6883
  }).optional().describe("Transient relay cursor returned by a previous read. Reconnects without replaying already-seen transcript or nudge frames."),
6725
- }, { title: "Read Live Transcript", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async ({ since, status_only, relay_cursor }) => {
6884
+ }, { title: "Read Live Transcript", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }, async ({ since, status_only, include_current, relay_cursor }) => {
6726
6885
  if (!(await isCliAvailable())) {
6727
6886
  return { content: [{ type: "text", text: CLI_INSTALL_MSG }] };
6728
6887
  }
@@ -6733,18 +6892,30 @@ registerTool("read_live_transcript", "Read the live transcript — works during
6733
6892
  else if (since) {
6734
6893
  args.push("--since", since);
6735
6894
  }
6736
- let relay;
6737
- try {
6738
- relay = await attachCaptureRelay(relay_cursor);
6895
+ if (include_current && !status_only) {
6896
+ args.push("--include-current");
6739
6897
  }
6740
- catch {
6741
- // A durable-only or inactive session is still readable. start_live_transcript
6742
- // reports attachment failures explicitly when another process owns capture.
6898
+ let relay;
6899
+ if (!include_current) {
6900
+ try {
6901
+ relay = await attachCaptureRelay(relay_cursor);
6902
+ }
6903
+ catch {
6904
+ // A durable-only or inactive session is still readable. start_live_transcript
6905
+ // reports attachment failures explicitly when another process owns capture.
6906
+ }
6743
6907
  }
6744
6908
  try {
6745
6909
  const { stdout } = await runMinutes(args, 10000);
6746
6910
  // For status queries, a message is helpful. For transcript reads, empty = no new lines.
6747
6911
  const fallback = status_only ? "No transcript data available." : "";
6912
+ if (include_current && !status_only) {
6913
+ const snapshot = parseJsonOutput(stdout || fallback);
6914
+ return {
6915
+ content: [{ type: "text", text: JSON.stringify(snapshot, null, 2) }],
6916
+ structuredContent: snapshot,
6917
+ };
6918
+ }
6748
6919
  if (relay) {
6749
6920
  const payload = {
6750
6921
  durable: parseJsonOutput(stdout || fallback),
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Environment for a helper that must run as Node through process.execPath.
3
+ *
4
+ * Electron hosts such as Claude Desktop expose their app executable as
5
+ * process.execPath. Spawning that executable with a JavaScript file does not
6
+ * start Node unless ELECTRON_RUN_AS_NODE is set for the child. Keep the switch
7
+ * scoped to helpers whose argv is a Node program; ordinary Minutes CLI children
8
+ * must continue to receive their existing policy environment unchanged.
9
+ */
10
+ export declare function nodeChildEnvironment(base?: NodeJS.ProcessEnv, electronVersion?: string | undefined): NodeJS.ProcessEnv;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Environment for a helper that must run as Node through process.execPath.
3
+ *
4
+ * Electron hosts such as Claude Desktop expose their app executable as
5
+ * process.execPath. Spawning that executable with a JavaScript file does not
6
+ * start Node unless ELECTRON_RUN_AS_NODE is set for the child. Keep the switch
7
+ * scoped to helpers whose argv is a Node program; ordinary Minutes CLI children
8
+ * must continue to receive their existing policy environment unchanged.
9
+ */
10
+ export function nodeChildEnvironment(base = process.env, electronVersion = process.versions.electron) {
11
+ const environment = { ...base };
12
+ if (electronVersion) {
13
+ environment.ELECTRON_RUN_AS_NODE = "1";
14
+ }
15
+ return environment;
16
+ }
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
3
3
  import { lstatSync, realpathSync, statSync, } from "node:fs";
4
4
  import { basename, dirname } from "path";
5
5
  import { TextDecoder } from "node:util";
6
+ import { nodeChildEnvironment } from "./node-child.js";
6
7
  /**
7
8
  * Decode security-policy-bearing text without Unicode replacement.
8
9
  *
@@ -353,7 +354,7 @@ class BoundParentReader {
353
354
  });
354
355
  this.child = spawn(process.execPath, ["-e", BOUND_READER_SOURCE], {
355
356
  cwd: parent,
356
- env: { ...process.env, MINUTES_BOUND_PARENT: parent },
357
+ env: nodeChildEnvironment({ ...process.env, MINUTES_BOUND_PARENT: parent }),
357
358
  stdio: ["pipe", "pipe", "pipe"],
358
359
  windowsHide: true,
359
360
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minutes-mcp",
3
- "version": "0.25.2",
3
+ "version": "0.25.4",
4
4
  "description": "MCP server for minutes \u2014 conversation memory for AI assistants. Works with Claude Desktop, Mistral Vibe, Cursor, Windsurf, and any MCP client.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -45,12 +45,12 @@
45
45
  "dependencies": {
46
46
  "@modelcontextprotocol/ext-apps": "^1.6.0",
47
47
  "@modelcontextprotocol/sdk": "^1.29.0",
48
- "minutes-sdk": "0.25.2",
48
+ "minutes-sdk": "0.25.4",
49
49
  "yaml": "^2.8.3",
50
50
  "zod": "^3.25 || ^4.0"
51
51
  },
52
52
  "devDependencies": {
53
- "@types/node": "^22.19.17",
53
+ "@types/node": "^26.2.0",
54
54
  "tsx": "^4.0.0",
55
55
  "typescript": "^5.5.0",
56
56
  "vite": "^7.3.2",