@yugabytedb/meko-mcp 2.3.0 → 2.5.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/README.md +72 -33
- package/assets/hooks-handlers/lib/capture-cleanup-cli.js +735 -74
- package/assets/hooks-handlers/lib/capture-notices.js +94 -1
- package/assets/hooks-handlers/lib/capture-state.js +111 -0
- package/assets/hooks-handlers/lib/capture.js +129 -4
- package/assets/hooks-handlers/lib/checkpoint-timer.js +394 -8
- package/assets/hooks-handlers/lib/opencode-handler.js +29 -6
- package/assets/scripts/meko-statusline.sh +17 -1
- package/assets/skills/meko-mcp-tools/SKILL.md +5 -5
- package/assets/skills/meko-mcp-tools/references/_sections.md +1 -1
- package/assets/skills/meko-mcp-tools/references/tools-cookbook.md +193 -9
- package/assets/skills/meko-mcp-tools/references/tools-datapack-workflow.md +6 -3
- package/assets/skills/meko-mcp-tools/references/tools-known-limitations.md +11 -6
- package/assets/skills/meko-mcp-tools/references/tools-memory-vs-conversation.md +4 -4
- package/assets/skills/meko-mcp-tools/references/tools-overview.md +16 -11
- package/assets/skills/meko-mcp-tools/references/tools-rag-workflow.md +67 -8
- package/assets/skills/meko-mcp-tools/references/tools-troubleshooting.md +3 -1
- package/assets/skills/meko-mcp-tools-desktop/SKILL.md +7 -7
- package/assets/skills/meko-mcp-tools-desktop/desktop/README.md +4 -4
- package/assets/skills/meko-mcp-tools-desktop/references/_sections.md +1 -1
- package/assets/skills/meko-mcp-tools-desktop/references/tools-cookbook.md +193 -9
- package/assets/skills/meko-mcp-tools-desktop/references/tools-known-limitations.md +10 -5
- package/assets/skills/meko-mcp-tools-desktop/references/tools-memory-vs-conversation.md +4 -4
- package/assets/skills/meko-mcp-tools-desktop/references/tools-overview.md +16 -11
- package/assets/skills/meko-mcp-tools-desktop/references/tools-rag-workflow.md +67 -8
- package/assets/skills/meko-mcp-tools-desktop/references/tools-troubleshooting.md +3 -1
- package/assets/skills/meko-select-datapack-desktop/SKILL.md +91 -12
- package/assets/skills/meko-select-datapack-desktop/references/cookbook.md +39 -7
- package/assets/skills/meko-select-datapack-desktop/references/role-display-future.md +8 -6
- package/assets/skills/meko-select-datapack-desktop/references/troubleshooting.md +13 -1
- package/bin/create-meko-setup.mjs +117 -24
- package/lib/autofix.mjs +100 -0
- package/lib/browser-login.mjs +495 -0
- package/lib/clients/opencode.mjs +6 -1
- package/lib/migrate/datapack.mjs +254 -16
- package/lib/migrate/mcp-client.mjs +34 -0
- package/lib/migrate/orchestrator.mjs +1 -1
- package/lib/migrate.mjs +1 -1
- package/lib/validate.mjs +111 -22
- package/package.json +5 -5
|
@@ -7,7 +7,8 @@ import { dirname, join } from 'node:path';
|
|
|
7
7
|
|
|
8
8
|
import { install, uninstall } from '../lib/installer.mjs';
|
|
9
9
|
import { askChoice, askMultiChoice, askText, askSecret, askYesNo, close as closePrompts } from '../lib/prompt.mjs';
|
|
10
|
-
import { banner, error, dim, info, warn } from '../lib/logger.mjs';
|
|
10
|
+
import { banner, error, dim, info, success, warn } from '../lib/logger.mjs';
|
|
11
|
+
import { BrowserLoginError, consoleUrlFor, loginAndCreateApiKey } from '../lib/browser-login.mjs';
|
|
11
12
|
import {
|
|
12
13
|
isPackagedInstall,
|
|
13
14
|
MEKO_PRODUCTION_CONSOLE_URL,
|
|
@@ -20,6 +21,7 @@ import { startMigration, runChildMigration, readStatus, stopMigration } from '..
|
|
|
20
21
|
import { checkLatest } from '../lib/version-check.mjs';
|
|
21
22
|
import { runCaptureStatus } from '../lib/capture-status.mjs';
|
|
22
23
|
import { captureCleanupArgs, runCaptureCleanup } from '../lib/capture-cleanup.mjs';
|
|
24
|
+
import { applyAutofixSetting, describeAutofix } from '../lib/autofix.mjs';
|
|
23
25
|
|
|
24
26
|
// ---------------------------------------------------------------------------
|
|
25
27
|
// Flag definitions
|
|
@@ -28,6 +30,7 @@ import { captureCleanupArgs, runCaptureCleanup } from '../lib/capture-cleanup.mj
|
|
|
28
30
|
const options = {
|
|
29
31
|
url: { type: 'string' },
|
|
30
32
|
'api-key': { type: 'string' },
|
|
33
|
+
'no-browser': { type: 'boolean', default: false },
|
|
31
34
|
local: { type: 'boolean', default: false },
|
|
32
35
|
scope: { type: 'string', default: 'user' },
|
|
33
36
|
name: { type: 'string', default: 'meko' },
|
|
@@ -37,6 +40,8 @@ const options = {
|
|
|
37
40
|
'skip-validation': { type: 'boolean', default: false },
|
|
38
41
|
statusline: { type: 'boolean', default: false },
|
|
39
42
|
'no-statusline': { type: 'boolean', default: false },
|
|
43
|
+
autofix: { type: 'boolean', default: false },
|
|
44
|
+
'no-autofix': { type: 'boolean', default: false },
|
|
40
45
|
'no-canary': { type: 'boolean', default: false },
|
|
41
46
|
'dry-run': { type: 'boolean', default: false },
|
|
42
47
|
'check-updates': { type: 'boolean', default: false },
|
|
@@ -96,7 +101,10 @@ Examples:
|
|
|
96
101
|
# Interactive setup (recommended for first-time users):
|
|
97
102
|
create-meko-setup
|
|
98
103
|
|
|
99
|
-
# Cloud Meko for Claude Code (
|
|
104
|
+
# Cloud Meko for Claude Code (signs in through your browser):
|
|
105
|
+
create-meko-setup --url https://mcp.mekodata.ai/mcp --client claude-code
|
|
106
|
+
|
|
107
|
+
# Cloud Meko with an existing API key (unattended, e.g. CI):
|
|
100
108
|
create-meko-setup --url https://mcp.mekodata.ai/mcp --api-key "$MEKO_API_KEY" --yes
|
|
101
109
|
|
|
102
110
|
# Local Meko with Cursor config:
|
|
@@ -131,7 +139,10 @@ Options:
|
|
|
131
139
|
untrusted IDE workspaces and non-managed CLI agents fall
|
|
132
140
|
back to agent-driven persistence.
|
|
133
141
|
--url <url> MCP server URL (from cloud.mekodata.ai > Datapacks > Connect)
|
|
134
|
-
--api-key <key>
|
|
142
|
+
--api-key <key> Use an existing Cloud Meko API key instead of signing in
|
|
143
|
+
through the browser. Also read from $MEKO_API_KEY.
|
|
144
|
+
Required when no terminal is attached (CI, scripts).
|
|
145
|
+
--no-browser Print the sign-in URL instead of opening a browser
|
|
135
146
|
--local Use local Meko at http://localhost:8000/mcp
|
|
136
147
|
--scope <scope> Config scope: user or project (default: user)
|
|
137
148
|
codex always installs at user scope (no project-level
|
|
@@ -144,8 +155,13 @@ Options:
|
|
|
144
155
|
Default: ON under --yes, prompted otherwise.
|
|
145
156
|
Claude Code only.
|
|
146
157
|
--no-statusline Skip statusline wiring even under --yes.
|
|
158
|
+
--no-autofix Turn off automatic capture repair: the capture timer
|
|
159
|
+
otherwise runs --capture-cleanup fix every 30 minutes
|
|
160
|
+
(MEKO_AUTOFIX_INTERVAL) when a session is stuck.
|
|
161
|
+
--autofix Turn automatic capture repair back on.
|
|
147
162
|
--skip-validation Skip post-install validation
|
|
148
|
-
--no-canary Skip the canary
|
|
163
|
+
--no-canary Skip the canary datapack_search (or, on older
|
|
164
|
+
servers, datapack_list) call. The
|
|
149
165
|
connectivity + tools/list checks still run.
|
|
150
166
|
--dry-run Show what would be done without making changes
|
|
151
167
|
--check-updates Compare installed version against npm registry and exit
|
|
@@ -167,6 +183,10 @@ Capture health (durable outbox / recovery):
|
|
|
167
183
|
Clean up stuck capture on this machine:
|
|
168
184
|
status list sessions with unsent work and the
|
|
169
185
|
background capture processes (read-only)
|
|
186
|
+
fix diagnose each stuck session and fix it:
|
|
187
|
+
repair, drain, or move unsent turns to a
|
|
188
|
+
new conversation; reports what only you
|
|
189
|
+
can fix (start here)
|
|
170
190
|
stop stop SessionEnd workers and in-flight
|
|
171
191
|
drains, and park all unsent work so no
|
|
172
192
|
hook retries it (nothing is deleted)
|
|
@@ -295,6 +315,32 @@ function parseRate(raw) {
|
|
|
295
315
|
return n;
|
|
296
316
|
}
|
|
297
317
|
|
|
318
|
+
// Placeholder shown in --dry-run output in place of a key that browser
|
|
319
|
+
// sign-in would create. Never sent anywhere: dry runs make no changes.
|
|
320
|
+
const DRY_RUN_SIGN_IN_KEY = 'mko_tkn_<created-by-browser-sign-in>';
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Sign in through the browser and create an API key for this install.
|
|
324
|
+
* Returns the key, or null after printing why sign-in failed.
|
|
325
|
+
*/
|
|
326
|
+
async function signInWithBrowser({ url, noBrowser, dryRun }) {
|
|
327
|
+
if (dryRun) {
|
|
328
|
+
dim('Would sign in with your browser and create an API key (skipped: --dry-run).');
|
|
329
|
+
return DRY_RUN_SIGN_IN_KEY;
|
|
330
|
+
}
|
|
331
|
+
info('Signing in to Meko with your browser to create an API key...');
|
|
332
|
+
try {
|
|
333
|
+
const key = await loginAndCreateApiKey({ url, noBrowser, notify: (msg) => dim(msg) });
|
|
334
|
+
const expires = key.expiresAt ? ` (expires ${String(key.expiresAt).slice(0, 10)})` : '';
|
|
335
|
+
success(`Signed in. Created API key "${key.name}"${expires}.`);
|
|
336
|
+
dim(`Manage or revoke it at ${consoleUrlFor(url)} > Settings > API Keys.`);
|
|
337
|
+
return key.apiKey;
|
|
338
|
+
} catch (err) {
|
|
339
|
+
error(err instanceof BrowserLoginError ? err.message : `Browser sign-in failed: ${err?.message ?? err}`);
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
298
344
|
// ---------------------------------------------------------------------------
|
|
299
345
|
// Main
|
|
300
346
|
// ---------------------------------------------------------------------------
|
|
@@ -340,7 +386,7 @@ async function main() {
|
|
|
340
386
|
if (out.stderr) process.stderr.write(out.stderr);
|
|
341
387
|
process.exit(out.code);
|
|
342
388
|
}
|
|
343
|
-
// --capture-cleanup <action>: inspect, stop, park, drain, or discard
|
|
389
|
+
// --capture-cleanup <action>: inspect, fix, stop, park, drain, or discard
|
|
344
390
|
// stuck capture sessions. Exit codes: 0 done, 1 error, 2 partly done.
|
|
345
391
|
if (flags['capture-cleanup'] !== undefined) {
|
|
346
392
|
const out = runCaptureCleanup(
|
|
@@ -508,6 +554,10 @@ async function main() {
|
|
|
508
554
|
// submitting an empty value at the full-flow prompt would re-ask the
|
|
509
555
|
// same question.
|
|
510
556
|
let promptedForApiKey = false;
|
|
557
|
+
// Set when the user picked "Sign in with browser" at the interactive auth
|
|
558
|
+
// prompt. Someone answered that prompt, so sign-in runs even when stdin
|
|
559
|
+
// is not a TTY (e.g. piped answers in the smoke harness).
|
|
560
|
+
let choseBrowserSignIn = false;
|
|
511
561
|
|
|
512
562
|
// --local shortcut
|
|
513
563
|
if (flags.local) {
|
|
@@ -591,7 +641,7 @@ async function main() {
|
|
|
591
641
|
// When only Cloud is available, skip the prompt entirely — asking to
|
|
592
642
|
// pick between one option is pointless UX.
|
|
593
643
|
const connectionOptions = [
|
|
594
|
-
{ label: 'Cloud Meko', description: 'Hosted at mekodata.ai (
|
|
644
|
+
{ label: 'Cloud Meko', description: 'Hosted at mekodata.ai (sign in with your browser)' },
|
|
595
645
|
];
|
|
596
646
|
if (!isPackagedInstall()) {
|
|
597
647
|
connectionOptions.push({
|
|
@@ -620,11 +670,24 @@ async function main() {
|
|
|
620
670
|
suggestedUrl?.url ?? undefined,
|
|
621
671
|
);
|
|
622
672
|
|
|
623
|
-
// Step 3 —
|
|
624
|
-
//
|
|
625
|
-
//
|
|
626
|
-
apiKey
|
|
627
|
-
|
|
673
|
+
// Step 3 — authentication. Browser sign-in is the default and runs
|
|
674
|
+
// in the Cloud-URL check below; pasting a key is the advanced path.
|
|
675
|
+
// An existing --api-key or $MEKO_API_KEY skips the question.
|
|
676
|
+
if (!apiKey && !normalizeApiKey(process.env.MEKO_API_KEY)) {
|
|
677
|
+
const authChoice = await askChoice('How do you want to authenticate?', [
|
|
678
|
+
{ label: 'Sign in with browser', description: 'Recommended. Creates an API key for you' },
|
|
679
|
+
{ label: 'Paste an API key', description: `From ${consoleUrlFor(url)} > Settings > API Keys` },
|
|
680
|
+
]);
|
|
681
|
+
if (authChoice === 'Paste an API key') {
|
|
682
|
+
// Normalize: terminal paste can drag in trailing whitespace/CR or
|
|
683
|
+
// accidental surrounding quotes, all of which would sail straight
|
|
684
|
+
// into `Authorization: Bearer ...` and 401.
|
|
685
|
+
apiKey = normalizeApiKey(await askSecret('API key:'));
|
|
686
|
+
promptedForApiKey = true;
|
|
687
|
+
} else {
|
|
688
|
+
choseBrowserSignIn = true;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
628
691
|
} else {
|
|
629
692
|
url = 'http://localhost:8000/mcp';
|
|
630
693
|
apiKey = null;
|
|
@@ -663,25 +726,38 @@ async function main() {
|
|
|
663
726
|
|
|
664
727
|
// Validation — Cloud URL requires an API key.
|
|
665
728
|
// Resolution order: --api-key (already in `apiKey`) → $MEKO_API_KEY →
|
|
666
|
-
//
|
|
667
|
-
//
|
|
668
|
-
//
|
|
669
|
-
//
|
|
670
|
-
//
|
|
671
|
-
//
|
|
729
|
+
// browser sign-in, which creates a key through the server's
|
|
730
|
+
// POST /api-keys. Sign-in also covers the cloud one-liner from the UI,
|
|
731
|
+
// which only shows --url and --client. It is skipped when the full
|
|
732
|
+
// interactive flow already took a pasted key (an empty paste is an
|
|
733
|
+
// error, not a silent switch to the browser), and whenever stdin is not
|
|
734
|
+
// a terminal, with or without --yes: a script or CI step may have nobody
|
|
735
|
+
// at the browser, and a 5-minute callback wait would stall it. The one
|
|
736
|
+
// exception is an explicit "Sign in with browser" answer at the prompt.
|
|
672
737
|
if (isCloudUrl(url) && !apiKey) {
|
|
673
738
|
apiKey = normalizeApiKey(process.env.MEKO_API_KEY);
|
|
739
|
+
let signInFailed = false;
|
|
674
740
|
if (apiKey) {
|
|
675
741
|
dim('Using API key from MEKO_API_KEY environment variable.');
|
|
676
|
-
} else if (!
|
|
677
|
-
|
|
678
|
-
|
|
742
|
+
} else if (!promptedForApiKey && (process.stdin.isTTY || choseBrowserSignIn)) {
|
|
743
|
+
apiKey = await signInWithBrowser({
|
|
744
|
+
url,
|
|
745
|
+
noBrowser: flags['no-browser'],
|
|
746
|
+
dryRun: flags['dry-run'],
|
|
747
|
+
});
|
|
748
|
+
signInFailed = !apiKey;
|
|
679
749
|
}
|
|
680
750
|
if (!apiKey) {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
)
|
|
751
|
+
let message;
|
|
752
|
+
if (signInFailed) {
|
|
753
|
+
message = `Browser sign-in did not complete. To use an existing key instead, create one at ${consoleUrlFor(url)} > Settings > API Keys and rerun with --api-key, or set MEKO_API_KEY.`;
|
|
754
|
+
} else if (promptedForApiKey) {
|
|
755
|
+
message = 'No API key entered. Rerun and choose Sign in with browser, or pass --api-key.';
|
|
756
|
+
} else {
|
|
757
|
+
message = 'API key is required for Cloud Meko. ' +
|
|
758
|
+
'Provide --api-key, set MEKO_API_KEY env var, or run in a terminal to sign in with your browser.';
|
|
759
|
+
}
|
|
760
|
+
error(message);
|
|
685
761
|
process.exit(1);
|
|
686
762
|
}
|
|
687
763
|
}
|
|
@@ -716,6 +792,23 @@ async function main() {
|
|
|
716
792
|
}
|
|
717
793
|
}
|
|
718
794
|
|
|
795
|
+
// Automatic capture repair runs inside the capture timer the hooks start,
|
|
796
|
+
// so installing the hooks deploys it. Save --autofix / --no-autofix once,
|
|
797
|
+
// for every client (they share one capture directory).
|
|
798
|
+
if (errors.length < clients.length) {
|
|
799
|
+
if (flags.autofix && flags['no-autofix']) {
|
|
800
|
+
warn('Both --autofix and --no-autofix were given; keeping the current setting.');
|
|
801
|
+
}
|
|
802
|
+
const enable =
|
|
803
|
+
flags['no-autofix'] && !flags.autofix ? false : flags.autofix && !flags['no-autofix'] ? true : undefined;
|
|
804
|
+
try {
|
|
805
|
+
const res = applyAutofixSetting({ enable, dryRun: flags['dry-run'], projectRoot });
|
|
806
|
+
info(describeAutofix(res.enabled));
|
|
807
|
+
} catch (err) {
|
|
808
|
+
warn(`Could not save the automatic repair setting: ${err.message}`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
719
812
|
if (errors.length === clients.length) {
|
|
720
813
|
process.exit(1);
|
|
721
814
|
} else if (errors.length > 0) {
|
package/lib/autofix.mjs
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* autofix.mjs: the installer side of automatic capture repair.
|
|
3
|
+
*
|
|
4
|
+
* The checkpoint timer runs `--capture-cleanup fix --auto` every
|
|
5
|
+
* MEKO_AUTOFIX_INTERVAL seconds. The on/off choice lives in
|
|
6
|
+
* autofix-config.json in the capture directory, which capture-state.js
|
|
7
|
+
* autofixSetting() reads. MEKO_AUTOFIX in the hook environment overrides it.
|
|
8
|
+
*
|
|
9
|
+
* Most clients share one capture directory, but OpenCode hooks keep the one
|
|
10
|
+
* saved in their hook env file at first install, so the file is written to
|
|
11
|
+
* every capture directory the installer knows about.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
16
|
+
|
|
17
|
+
import { getCaptureRoot, getOpenCodeHookEnvPath } from "./config.mjs";
|
|
18
|
+
|
|
19
|
+
const CONFIG_FILE = "autofix-config.json";
|
|
20
|
+
|
|
21
|
+
function savedOpenCodeRoot(scope, projectRoot) {
|
|
22
|
+
try {
|
|
23
|
+
const file = getOpenCodeHookEnvPath(scope, projectRoot);
|
|
24
|
+
if (!existsSync(file)) return null;
|
|
25
|
+
const value = JSON.parse(readFileSync(file, "utf8")).MEKO_WATERMARK_DIR;
|
|
26
|
+
if (typeof value !== "string" || !value) return null;
|
|
27
|
+
return isAbsolute(value) ? value : resolve(value);
|
|
28
|
+
} catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Every capture directory a client's timer may read the setting from. */
|
|
34
|
+
export function captureRoots({ projectRoot = process.cwd() } = {}) {
|
|
35
|
+
const roots = [getCaptureRoot()];
|
|
36
|
+
for (const scope of ["user", "project"]) {
|
|
37
|
+
const saved = savedOpenCodeRoot(scope, projectRoot);
|
|
38
|
+
if (saved && !roots.includes(saved)) roots.push(saved);
|
|
39
|
+
}
|
|
40
|
+
return roots;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function autofixConfigPath() {
|
|
44
|
+
return join(getCaptureRoot(), CONFIG_FILE);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @returns {boolean|null} the saved choice in the main capture directory, or null. */
|
|
48
|
+
export function readAutofixSetting() {
|
|
49
|
+
try {
|
|
50
|
+
const cfg = JSON.parse(readFileSync(autofixConfigPath(), "utf8"));
|
|
51
|
+
return typeof cfg.enabled === "boolean" ? cfg.enabled : null;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Save --autofix / --no-autofix in every capture directory. `enable`
|
|
59
|
+
* undefined keeps an earlier choice, so a plain reinstall doesn't turn
|
|
60
|
+
* repair back on.
|
|
61
|
+
*
|
|
62
|
+
* @param {{ enable?: boolean, dryRun?: boolean, projectRoot?: string }} opts
|
|
63
|
+
* @returns {{ enabled: boolean, changed: boolean, paths: string[] }}
|
|
64
|
+
*/
|
|
65
|
+
export function applyAutofixSetting({ enable, dryRun = false, projectRoot } = {}) {
|
|
66
|
+
const saved = readAutofixSetting();
|
|
67
|
+
const paths = captureRoots({ projectRoot }).map((root) => join(root, CONFIG_FILE));
|
|
68
|
+
if (enable === undefined) {
|
|
69
|
+
return { enabled: saved === null ? true : saved, changed: false, paths };
|
|
70
|
+
}
|
|
71
|
+
if (!dryRun) {
|
|
72
|
+
for (const file of paths) {
|
|
73
|
+
mkdirSync(dirname(file), { recursive: true, mode: 0o700 });
|
|
74
|
+
writeFileSync(
|
|
75
|
+
file,
|
|
76
|
+
`${JSON.stringify({ enabled: enable, updated_at: new Date().toISOString() })}\n`,
|
|
77
|
+
{ mode: 0o600 },
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return { enabled: enable, changed: saved !== enable, paths };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Same rule as capture-state.js autofixIntervalSeconds(). */
|
|
85
|
+
function intervalSeconds(env) {
|
|
86
|
+
const n = Number.parseInt(env.MEKO_AUTOFIX_INTERVAL || "", 10);
|
|
87
|
+
return Math.max(60, Number.isFinite(n) ? n : 1800);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** One line for the end of an install. */
|
|
91
|
+
export function describeAutofix(enabled, env = process.env) {
|
|
92
|
+
const envOverride = env.MEKO_AUTOFIX;
|
|
93
|
+
if (envOverride != null && envOverride !== "") {
|
|
94
|
+
return `Automatic capture repair follows MEKO_AUTOFIX=${envOverride} in this environment.`;
|
|
95
|
+
}
|
|
96
|
+
const minutes = Math.max(1, Math.round(intervalSeconds(env) / 60));
|
|
97
|
+
return enabled
|
|
98
|
+
? `Automatic capture repair is on: every ${minutes} minute${minutes === 1 ? "" : "s"} the capture timer fixes stuck sessions (--capture-cleanup fix). Turn it off with --no-autofix.`
|
|
99
|
+
: "Automatic capture repair is off. Turn it on with --autofix, or run --capture-cleanup fix yourself.";
|
|
100
|
+
}
|