minutes-mcp 0.25.3 → 0.25.5
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 +24 -0
- package/dist/index.js +159 -34
- package/package.json +3 -3
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>;
|
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ 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";
|
|
@@ -1570,7 +1570,7 @@ const COPILOT_SUPPORTED = hasFeature(CLI_CAPABILITIES, "copilot_realtime");
|
|
|
1570
1570
|
// `./version.ts` (see issue #183). Hosted `.mcpb` bundles will run
|
|
1571
1571
|
// against CLIs with different minor/patch numbers within the same
|
|
1572
1572
|
// major; that is explicitly supported.
|
|
1573
|
-
const MCP_SERVER_VERSION = "0.25.
|
|
1573
|
+
const MCP_SERVER_VERSION = "0.25.5";
|
|
1574
1574
|
export function parseKnowledgeConfig(configContent) {
|
|
1575
1575
|
const knowledgeMatch = configContent.match(/\[knowledge\][\s\S]*?(?=\n\[|$)/);
|
|
1576
1576
|
if (!knowledgeMatch) {
|
|
@@ -1777,38 +1777,150 @@ async function checkCliVersion() {
|
|
|
1777
1777
|
// Version check is best-effort. Don't block on failure.
|
|
1778
1778
|
}
|
|
1779
1779
|
}
|
|
1780
|
-
// ── Auto-setup: download whisper model if missing ───────────
|
|
1781
|
-
//
|
|
1782
|
-
//
|
|
1783
|
-
//
|
|
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.
|
|
1784
1784
|
let modelCheckDone = false;
|
|
1785
|
-
|
|
1786
|
-
|
|
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)
|
|
1787
1842
|
return;
|
|
1788
|
-
|
|
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;
|
|
1789
1863
|
try {
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
const
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
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}`);
|
|
1797
1908
|
return;
|
|
1798
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)...");
|
|
1799
1916
|
}
|
|
1800
|
-
catch {
|
|
1801
|
-
// health command may not exist in older CLI versions — fall through to setup
|
|
1802
|
-
}
|
|
1803
|
-
// Model not found — download tiny model in background
|
|
1804
|
-
console.error("[Minutes] Whisper model not found — downloading tiny model (~75MB)...");
|
|
1805
1917
|
try {
|
|
1806
|
-
await
|
|
1807
|
-
|
|
1918
|
+
await setup(model);
|
|
1919
|
+
log(`[Minutes] ✓ Whisper ${model} model downloaded — recording is ready`);
|
|
1808
1920
|
}
|
|
1809
|
-
catch (
|
|
1810
|
-
|
|
1811
|
-
|
|
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}`);
|
|
1812
1924
|
}
|
|
1813
1925
|
}
|
|
1814
1926
|
// ── CLI availability detection ──────────────────────────────
|
|
@@ -6760,15 +6872,16 @@ registerTool("start_live_transcript", "Start real-time transcription. If a recor
|
|
|
6760
6872
|
};
|
|
6761
6873
|
});
|
|
6762
6874
|
// ── Tool: read_live_transcript ──────────────────────────────
|
|
6763
|
-
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.", {
|
|
6764
6876
|
since: z.string().optional().describe("Line number (e.g., '42') or duration (e.g., '5m', '30s'). Omit to get all lines."),
|
|
6765
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"),
|
|
6766
6879
|
relay_cursor: z.object({
|
|
6767
6880
|
session_id: z.string().optional(),
|
|
6768
6881
|
transcript_seq: z.number().int().nonnegative(),
|
|
6769
6882
|
nudge_seq: z.number().int().nonnegative(),
|
|
6770
6883
|
}).optional().describe("Transient relay cursor returned by a previous read. Reconnects without replaying already-seen transcript or nudge frames."),
|
|
6771
|
-
}, { 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 }) => {
|
|
6772
6885
|
if (!(await isCliAvailable())) {
|
|
6773
6886
|
return { content: [{ type: "text", text: CLI_INSTALL_MSG }] };
|
|
6774
6887
|
}
|
|
@@ -6779,18 +6892,30 @@ registerTool("read_live_transcript", "Read the live transcript — works during
|
|
|
6779
6892
|
else if (since) {
|
|
6780
6893
|
args.push("--since", since);
|
|
6781
6894
|
}
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
relay = await attachCaptureRelay(relay_cursor);
|
|
6895
|
+
if (include_current && !status_only) {
|
|
6896
|
+
args.push("--include-current");
|
|
6785
6897
|
}
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
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
|
+
}
|
|
6789
6907
|
}
|
|
6790
6908
|
try {
|
|
6791
6909
|
const { stdout } = await runMinutes(args, 10000);
|
|
6792
6910
|
// For status queries, a message is helpful. For transcript reads, empty = no new lines.
|
|
6793
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
|
+
}
|
|
6794
6919
|
if (relay) {
|
|
6795
6920
|
const payload = {
|
|
6796
6921
|
durable: parseJsonOutput(stdout || fallback),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minutes-mcp",
|
|
3
|
-
"version": "0.25.
|
|
3
|
+
"version": "0.25.5",
|
|
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.
|
|
48
|
+
"minutes-sdk": "0.25.5",
|
|
49
49
|
"yaml": "^2.8.3",
|
|
50
50
|
"zod": "^3.25 || ^4.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@types/node": "^
|
|
53
|
+
"@types/node": "^26.2.0",
|
|
54
54
|
"tsx": "^4.0.0",
|
|
55
55
|
"typescript": "^5.5.0",
|
|
56
56
|
"vite": "^7.3.2",
|