@matthesketh/fleet 1.1.0 → 1.6.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 +183 -251
- package/dist/adapters/detector/index.d.ts +8 -0
- package/dist/adapters/detector/index.js +54 -0
- package/dist/adapters/notifier/index.d.ts +2 -0
- package/dist/adapters/notifier/index.js +2 -0
- package/dist/adapters/notifier/stdout.d.ts +2 -0
- package/dist/adapters/notifier/stdout.js +8 -0
- package/dist/adapters/notifier/webhook.d.ts +9 -0
- package/dist/adapters/notifier/webhook.js +38 -0
- package/dist/adapters/runner/claude-cli.d.ts +7 -0
- package/dist/adapters/runner/claude-cli.js +231 -0
- package/dist/adapters/runner/mcp-call.d.ts +8 -0
- package/dist/adapters/runner/mcp-call.js +82 -0
- package/dist/adapters/runner/shell.d.ts +2 -0
- package/dist/adapters/runner/shell.js +103 -0
- package/dist/adapters/scheduler/systemd-timer.d.ts +17 -0
- package/dist/adapters/scheduler/systemd-timer.js +149 -0
- package/dist/adapters/signals/ci-status.d.ts +2 -0
- package/dist/adapters/signals/ci-status.js +79 -0
- package/dist/adapters/signals/container-up.d.ts +5 -0
- package/dist/adapters/signals/container-up.js +54 -0
- package/dist/adapters/signals/git-clean.d.ts +2 -0
- package/dist/adapters/signals/git-clean.js +55 -0
- package/dist/adapters/signals/index.d.ts +6 -0
- package/dist/adapters/signals/index.js +7 -0
- package/dist/adapters/types.d.ts +52 -0
- package/dist/adapters/types.js +1 -0
- package/dist/cli.js +43 -2
- package/dist/commands/add.js +0 -6
- package/dist/commands/boot-start.d.ts +1 -0
- package/dist/commands/boot-start.js +51 -0
- package/dist/commands/deploy.js +13 -0
- package/dist/commands/deps.js +5 -0
- package/dist/commands/egress.d.ts +1 -0
- package/dist/commands/egress.js +106 -0
- package/dist/commands/freeze.d.ts +4 -0
- package/dist/commands/freeze.js +64 -0
- package/dist/commands/logs.d.ts +1 -1
- package/dist/commands/logs.js +237 -8
- package/dist/commands/patch-systemd.d.ts +1 -0
- package/dist/commands/patch-systemd.js +126 -0
- package/dist/commands/rollback.d.ts +1 -0
- package/dist/commands/rollback.js +58 -0
- package/dist/commands/routine-run.d.ts +1 -0
- package/dist/commands/routine-run.js +122 -0
- package/dist/commands/routines.d.ts +1 -0
- package/dist/commands/routines.js +25 -0
- package/dist/commands/secrets.js +449 -16
- package/dist/commands/status.js +7 -3
- package/dist/commands/watchdog.d.ts +1 -1
- package/dist/commands/watchdog.js +16 -40
- package/dist/core/boot-refresh.d.ts +57 -0
- package/dist/core/boot-refresh.js +116 -0
- package/dist/core/deps/actors/pr-creator.js +11 -9
- package/dist/core/deps/collectors/docker-running.js +2 -2
- package/dist/core/deps/collectors/github-pr.js +5 -2
- package/dist/core/deps/collectors/npm.js +10 -5
- package/dist/core/deps/collectors/vulnerability.js +10 -6
- package/dist/core/deps/reporters/motd.js +1 -1
- package/dist/core/deps/reporters/telegram.js +2 -29
- package/dist/core/docker.js +45 -15
- package/dist/core/egress.d.ts +41 -0
- package/dist/core/egress.js +161 -0
- package/dist/core/exec.d.ts +7 -1
- package/dist/core/exec.js +25 -17
- package/dist/core/git.d.ts +1 -0
- package/dist/core/git.js +36 -23
- package/dist/core/github.js +27 -8
- package/dist/core/health.d.ts +3 -0
- package/dist/core/health.js +15 -3
- package/dist/core/logs-multi.d.ts +73 -0
- package/dist/core/logs-multi.js +163 -0
- package/dist/core/logs-policy.d.ts +55 -0
- package/dist/core/logs-policy.js +148 -0
- package/dist/core/nginx.js +8 -4
- package/dist/core/notify.d.ts +15 -0
- package/dist/core/notify.js +55 -0
- package/dist/core/registry.d.ts +25 -0
- package/dist/core/registry.js +57 -10
- package/dist/core/routines/cost-queries.d.ts +24 -0
- package/dist/core/routines/cost-queries.js +65 -0
- package/dist/core/routines/db.d.ts +9 -0
- package/dist/core/routines/db.js +126 -0
- package/dist/core/routines/defaults.d.ts +2 -0
- package/dist/core/routines/defaults.js +72 -0
- package/dist/core/routines/engine.d.ts +59 -0
- package/dist/core/routines/engine.js +175 -0
- package/dist/core/routines/incidents.d.ts +13 -0
- package/dist/core/routines/incidents.js +35 -0
- package/dist/core/routines/schema.d.ts +418 -0
- package/dist/core/routines/schema.js +113 -0
- package/dist/core/routines/signals-collector.d.ts +35 -0
- package/dist/core/routines/signals-collector.js +114 -0
- package/dist/core/routines/store.d.ts +316 -0
- package/dist/core/routines/store.js +99 -0
- package/dist/core/routines/test-utils.d.ts +2 -0
- package/dist/core/routines/test-utils.js +13 -0
- package/dist/core/secrets-audit.d.ts +21 -0
- package/dist/core/secrets-audit.js +60 -0
- package/dist/core/secrets-metadata.d.ts +39 -0
- package/dist/core/secrets-metadata.js +82 -0
- package/dist/core/secrets-motd.d.ts +20 -0
- package/dist/core/secrets-motd.js +72 -0
- package/dist/core/secrets-ops.d.ts +3 -1
- package/dist/core/secrets-ops.js +78 -13
- package/dist/core/secrets-providers.d.ts +50 -0
- package/dist/core/secrets-providers.js +291 -0
- package/dist/core/secrets-rotation.d.ts +52 -0
- package/dist/core/secrets-rotation.js +165 -0
- package/dist/core/secrets-snapshots.d.ts +26 -0
- package/dist/core/secrets-snapshots.js +95 -0
- package/dist/core/secrets-validate.js +2 -1
- package/dist/core/secrets.d.ts +12 -1
- package/dist/core/secrets.js +35 -24
- package/dist/core/self-update.d.ts +41 -0
- package/dist/core/self-update.js +73 -0
- package/dist/core/systemd.js +29 -12
- package/dist/core/telegram.d.ts +6 -0
- package/dist/core/telegram.js +32 -0
- package/dist/core/validate.d.ts +7 -0
- package/dist/core/validate.js +42 -0
- package/dist/index.js +0 -4
- package/dist/mcp/deps-tools.js +9 -1
- package/dist/mcp/git-tools.js +4 -4
- package/dist/mcp/server.js +193 -8
- package/dist/templates/systemd.js +3 -3
- package/dist/templates/unseal.js +5 -1
- package/dist/tui/components/Confirm.js +3 -4
- package/dist/tui/components/Header.js +37 -8
- package/dist/tui/components/KeyHint.js +14 -5
- package/dist/tui/exec-bridge.js +26 -12
- package/dist/tui/hooks/use-fleet-data.js +5 -2
- package/dist/tui/hooks/use-health.js +5 -2
- package/dist/tui/hooks/use-terminal-size.d.ts +1 -0
- package/dist/tui/hooks/use-terminal-size.js +1 -0
- package/dist/tui/router.js +133 -8
- package/dist/tui/routines/RoutinesApp.d.ts +8 -0
- package/dist/tui/routines/RoutinesApp.js +277 -0
- package/dist/tui/routines/components/AlertsPanel.d.ts +7 -0
- package/dist/tui/routines/components/AlertsPanel.js +22 -0
- package/dist/tui/routines/components/AlertsPanel.test.d.ts +1 -0
- package/dist/tui/routines/components/AlertsPanel.test.js +52 -0
- package/dist/tui/routines/components/CommandPalette.d.ts +12 -0
- package/dist/tui/routines/components/CommandPalette.js +21 -0
- package/dist/tui/routines/components/LiveRunPanel.d.ts +12 -0
- package/dist/tui/routines/components/LiveRunPanel.js +107 -0
- package/dist/tui/routines/components/RoutineForm.d.ts +8 -0
- package/dist/tui/routines/components/RoutineForm.js +254 -0
- package/dist/tui/routines/components/SignalsGrid.d.ts +13 -0
- package/dist/tui/routines/components/SignalsGrid.js +34 -0
- package/dist/tui/routines/components/SignalsGrid.test.d.ts +1 -0
- package/dist/tui/routines/components/SignalsGrid.test.js +43 -0
- package/dist/tui/routines/format.d.ts +7 -0
- package/dist/tui/routines/format.js +51 -0
- package/dist/tui/routines/hooks/use-git-fleet.d.ts +33 -0
- package/dist/tui/routines/hooks/use-git-fleet.js +82 -0
- package/dist/tui/routines/hooks/use-logs-stream.d.ts +13 -0
- package/dist/tui/routines/hooks/use-logs-stream.js +64 -0
- package/dist/tui/routines/hooks/use-ops-fleet.d.ts +20 -0
- package/dist/tui/routines/hooks/use-ops-fleet.js +70 -0
- package/dist/tui/routines/hooks/use-repo-detail.d.ts +31 -0
- package/dist/tui/routines/hooks/use-repo-detail.js +104 -0
- package/dist/tui/routines/hooks/use-security.d.ts +33 -0
- package/dist/tui/routines/hooks/use-security.js +110 -0
- package/dist/tui/routines/hooks/use-signals.d.ts +9 -0
- package/dist/tui/routines/hooks/use-signals.js +60 -0
- package/dist/tui/routines/runtime.d.ts +20 -0
- package/dist/tui/routines/runtime.js +40 -0
- package/dist/tui/routines/tabs/CostTab.d.ts +7 -0
- package/dist/tui/routines/tabs/CostTab.js +24 -0
- package/dist/tui/routines/tabs/DashboardTab.d.ts +15 -0
- package/dist/tui/routines/tabs/DashboardTab.js +10 -0
- package/dist/tui/routines/tabs/GitTab.d.ts +6 -0
- package/dist/tui/routines/tabs/GitTab.js +39 -0
- package/dist/tui/routines/tabs/LogsTab.d.ts +6 -0
- package/dist/tui/routines/tabs/LogsTab.js +58 -0
- package/dist/tui/routines/tabs/OpsTab.d.ts +6 -0
- package/dist/tui/routines/tabs/OpsTab.js +34 -0
- package/dist/tui/routines/tabs/RepoDetailView.d.ts +6 -0
- package/dist/tui/routines/tabs/RepoDetailView.js +12 -0
- package/dist/tui/routines/tabs/RoutinesTab.d.ts +10 -0
- package/dist/tui/routines/tabs/RoutinesTab.js +58 -0
- package/dist/tui/routines/tabs/ScaffoldTab.d.ts +2 -0
- package/dist/tui/routines/tabs/ScaffoldTab.js +127 -0
- package/dist/tui/routines/tabs/SecurityTab.d.ts +6 -0
- package/dist/tui/routines/tabs/SecurityTab.js +31 -0
- package/dist/tui/routines/tabs/SettingsTab.d.ts +6 -0
- package/dist/tui/routines/tabs/SettingsTab.js +61 -0
- package/dist/tui/routines/tabs/TimelineTab.d.ts +7 -0
- package/dist/tui/routines/tabs/TimelineTab.js +26 -0
- package/dist/tui/state.js +16 -1
- package/dist/tui/tests/flicker.test.d.ts +1 -0
- package/dist/tui/tests/flicker.test.js +105 -0
- package/dist/tui/tests/keyboard-integration.test.d.ts +1 -0
- package/dist/tui/tests/keyboard-integration.test.js +120 -0
- package/dist/tui/tests/test-app.d.ts +4 -0
- package/dist/tui/tests/test-app.js +79 -0
- package/dist/tui/types.d.ts +14 -1
- package/dist/tui/views/AppDetail.js +40 -26
- package/dist/tui/views/Dashboard.js +34 -9
- package/dist/tui/views/HealthView.js +42 -12
- package/dist/tui/views/LogsView.js +38 -10
- package/dist/tui/views/MultiLogsView.d.ts +2 -0
- package/dist/tui/views/MultiLogsView.js +165 -0
- package/dist/tui/views/SecretEdit.js +18 -7
- package/dist/tui/views/SecretsView.js +55 -39
- package/dist/ui/prompt.d.ts +52 -0
- package/dist/ui/prompt.js +169 -0
- package/package.json +33 -5
- package/dist/commands/motd.d.ts +0 -1
- package/dist/commands/motd.js +0 -10
- package/dist/templates/motd.d.ts +0 -1
- package/dist/templates/motd.js +0 -7
- package/dist/tui/components/AppList.d.ts +0 -12
- package/dist/tui/components/AppList.js +0 -32
- package/dist/tui/hooks/use-keyboard.d.ts +0 -1
- package/dist/tui/hooks/use-keyboard.js +0 -44
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useRef } from 'react';
|
|
3
|
-
import { Box, Text
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
4
|
import Spinner from 'ink-spinner';
|
|
5
|
+
import { useRegisterHandler } from '@matthesketh/ink-input-dispatcher';
|
|
6
|
+
import { useAvailableHeight } from '@matthesketh/ink-viewport';
|
|
5
7
|
import { useAppState, useAppDispatch, useRedact } from '../state.js';
|
|
6
8
|
import { runFleetCommand, streamFleetCommand } from '../exec-bridge.js';
|
|
7
9
|
import { colors } from '../theme.js';
|
|
8
|
-
const MAX_LINES =
|
|
10
|
+
const MAX_LINES = 200;
|
|
9
11
|
export function LogsView() {
|
|
10
12
|
const { selectedApp } = useAppState();
|
|
11
13
|
const dispatch = useAppDispatch();
|
|
12
14
|
const redact = useRedact();
|
|
15
|
+
const availableHeight = useAvailableHeight();
|
|
13
16
|
const [lines, setLines] = useState([]);
|
|
14
17
|
const [following, setFollowing] = useState(false);
|
|
15
18
|
const [loading, setLoading] = useState(true);
|
|
16
19
|
const streamRef = useRef(null);
|
|
20
|
+
const lineBufferRef = useRef([]);
|
|
21
|
+
const flushTimerRef = useRef(null);
|
|
17
22
|
useEffect(() => {
|
|
18
23
|
if (!selectedApp)
|
|
19
24
|
return;
|
|
@@ -32,40 +37,63 @@ export function LogsView() {
|
|
|
32
37
|
streamRef.current.kill();
|
|
33
38
|
streamRef.current = null;
|
|
34
39
|
}
|
|
40
|
+
if (flushTimerRef.current) {
|
|
41
|
+
clearInterval(flushTimerRef.current);
|
|
42
|
+
flushTimerRef.current = null;
|
|
43
|
+
}
|
|
35
44
|
};
|
|
36
45
|
}, [selectedApp]);
|
|
37
|
-
|
|
46
|
+
const handler = (input, key) => {
|
|
38
47
|
if (input === 'f') {
|
|
39
48
|
if (following) {
|
|
40
|
-
// Stop following
|
|
41
49
|
if (streamRef.current) {
|
|
42
50
|
streamRef.current.kill();
|
|
43
51
|
streamRef.current = null;
|
|
44
52
|
}
|
|
53
|
+
if (flushTimerRef.current) {
|
|
54
|
+
clearInterval(flushTimerRef.current);
|
|
55
|
+
flushTimerRef.current = null;
|
|
56
|
+
}
|
|
57
|
+
// flush any remaining buffered lines
|
|
58
|
+
if (lineBufferRef.current.length > 0) {
|
|
59
|
+
const buf = lineBufferRef.current;
|
|
60
|
+
lineBufferRef.current = [];
|
|
61
|
+
setLines(prev => [...prev, ...buf].slice(-MAX_LINES));
|
|
62
|
+
}
|
|
45
63
|
setFollowing(false);
|
|
46
64
|
}
|
|
47
65
|
else if (selectedApp) {
|
|
48
|
-
// Start following
|
|
49
66
|
setFollowing(true);
|
|
50
67
|
const handle = streamFleetCommand(['logs', selectedApp, '-f']);
|
|
51
68
|
streamRef.current = handle;
|
|
52
69
|
handle.onData((line) => {
|
|
53
|
-
|
|
70
|
+
lineBufferRef.current.push(line);
|
|
54
71
|
});
|
|
72
|
+
flushTimerRef.current = setInterval(() => {
|
|
73
|
+
const buf = lineBufferRef.current;
|
|
74
|
+
if (buf.length === 0)
|
|
75
|
+
return;
|
|
76
|
+
lineBufferRef.current = [];
|
|
77
|
+
setLines(prev => [...prev, ...buf].slice(-MAX_LINES));
|
|
78
|
+
}, 50);
|
|
55
79
|
}
|
|
80
|
+
return true;
|
|
56
81
|
}
|
|
57
|
-
|
|
82
|
+
if (key.escape) {
|
|
58
83
|
if (streamRef.current) {
|
|
59
84
|
streamRef.current.kill();
|
|
60
85
|
streamRef.current = null;
|
|
61
86
|
}
|
|
62
87
|
dispatch({ type: 'GO_BACK' });
|
|
88
|
+
return true;
|
|
63
89
|
}
|
|
64
|
-
|
|
90
|
+
return false;
|
|
91
|
+
};
|
|
92
|
+
useRegisterHandler(handler);
|
|
65
93
|
if (loading) {
|
|
66
94
|
return (_jsx(Box, { padding: 1, children: _jsxs(Text, { children: [_jsx(Spinner, { type: "dots" }), " Loading logs for ", selectedApp, "..."] }) }));
|
|
67
95
|
}
|
|
68
|
-
|
|
69
|
-
const visibleLines = lines.slice(-
|
|
96
|
+
const visibleCount = Math.max(5, availableHeight - 3);
|
|
97
|
+
const visibleLines = lines.slice(-visibleCount);
|
|
70
98
|
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, gap: 2, children: [_jsxs(Text, { bold: true, color: colors.primary, children: ["Logs: ", redact(selectedApp ?? '')] }), following && (_jsxs(Text, { color: colors.success, children: [_jsx(Spinner, { type: "dots" }), " following"] }))] }), _jsx(Box, { flexDirection: "column", children: visibleLines.map((line, i) => (_jsx(Text, { wrap: "truncate", children: line }, i))) })] }));
|
|
71
99
|
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
import { useRegisterHandler } from '@matthesketh/ink-input-dispatcher';
|
|
6
|
+
import { useAvailableHeight } from '@matthesketh/ink-viewport';
|
|
7
|
+
import { colors } from '../theme.js';
|
|
8
|
+
import { useRedact } from '../state.js';
|
|
9
|
+
import { load } from '../../core/registry.js';
|
|
10
|
+
import { startMultiTail, resolveSources, } from '../../core/logs-multi.js';
|
|
11
|
+
const MAX_LINES = 500;
|
|
12
|
+
const LEVEL_RANKS = ['debug', 'info', 'warn', 'error', 'all'];
|
|
13
|
+
const LEVEL_RANK_NUMBER = {
|
|
14
|
+
debug: 0, info: 1, warn: 2, error: 3,
|
|
15
|
+
};
|
|
16
|
+
const SOURCE_PALETTE = [colors.primary, colors.success, colors.warning, colors.muted, 'cyan', 'magenta'];
|
|
17
|
+
function colourForSource(name) {
|
|
18
|
+
let h = 0;
|
|
19
|
+
for (let i = 0; i < name.length; i++)
|
|
20
|
+
h = (h * 31 + name.charCodeAt(i)) | 0;
|
|
21
|
+
return SOURCE_PALETTE[Math.abs(h) % SOURCE_PALETTE.length];
|
|
22
|
+
}
|
|
23
|
+
export function MultiLogsView() {
|
|
24
|
+
const redact = useRedact();
|
|
25
|
+
const availableHeight = useAvailableHeight();
|
|
26
|
+
const allSources = useMemo(() => {
|
|
27
|
+
try {
|
|
28
|
+
return resolveSources(load().apps);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
// Selection: by default, every container. User toggles with Space.
|
|
35
|
+
const [selected, setSelected] = useState(() => new Set(allSources.map(s => `${s.app}/${s.container}`)));
|
|
36
|
+
const [pickerIndex, setPickerIndex] = useState(0);
|
|
37
|
+
const [paused, setPaused] = useState(false);
|
|
38
|
+
const [level, setLevel] = useState('all');
|
|
39
|
+
const [lines, setLines] = useState([]);
|
|
40
|
+
const [pickerFocused, setPickerFocused] = useState(true);
|
|
41
|
+
const handleRef = useRef(null);
|
|
42
|
+
const lineIdRef = useRef(0);
|
|
43
|
+
// Buffer lines between renders so we batch React updates and don't flicker
|
|
44
|
+
// when bursts of output land. flushed every 100ms.
|
|
45
|
+
const pendingRef = useRef([]);
|
|
46
|
+
// (Re)start tailers whenever the selection changes.
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
if (handleRef.current) {
|
|
49
|
+
void handleRef.current.stop();
|
|
50
|
+
handleRef.current = null;
|
|
51
|
+
}
|
|
52
|
+
pendingRef.current = [];
|
|
53
|
+
setLines([]);
|
|
54
|
+
const sources = allSources.filter(s => selected.has(`${s.app}/${s.container}`));
|
|
55
|
+
if (sources.length === 0)
|
|
56
|
+
return;
|
|
57
|
+
const handle = startMultiTail(sources, { tail: 30, follow: true }, line => {
|
|
58
|
+
pendingRef.current.push({ ...line, id: ++lineIdRef.current });
|
|
59
|
+
});
|
|
60
|
+
handleRef.current = handle;
|
|
61
|
+
return () => { void handle.stop(); };
|
|
62
|
+
}, [allSources, selected]);
|
|
63
|
+
// Flush buffered lines into state on a 100ms tick (batched to avoid flicker).
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const t = setInterval(() => {
|
|
66
|
+
if (paused)
|
|
67
|
+
return;
|
|
68
|
+
if (pendingRef.current.length === 0)
|
|
69
|
+
return;
|
|
70
|
+
const batch = pendingRef.current;
|
|
71
|
+
pendingRef.current = [];
|
|
72
|
+
setLines(prev => {
|
|
73
|
+
const merged = prev.length + batch.length > MAX_LINES
|
|
74
|
+
? [...prev, ...batch].slice(-MAX_LINES)
|
|
75
|
+
: [...prev, ...batch];
|
|
76
|
+
return merged;
|
|
77
|
+
});
|
|
78
|
+
}, 100);
|
|
79
|
+
return () => clearInterval(t);
|
|
80
|
+
}, [paused]);
|
|
81
|
+
const handler = (input, key) => {
|
|
82
|
+
// Tab toggles focus between picker and viewport (so j/k goes to the right place).
|
|
83
|
+
if (key.tab) {
|
|
84
|
+
setPickerFocused(p => !p);
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (pickerFocused) {
|
|
88
|
+
if (input === 'j' || key.downArrow) {
|
|
89
|
+
setPickerIndex(i => Math.min(i + 1, allSources.length - 1));
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (input === 'k' || key.upArrow) {
|
|
93
|
+
setPickerIndex(i => Math.max(i - 1, 0));
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (input === ' ') {
|
|
97
|
+
const src = allSources[pickerIndex];
|
|
98
|
+
if (!src)
|
|
99
|
+
return true;
|
|
100
|
+
const k = `${src.app}/${src.container}`;
|
|
101
|
+
setSelected(prev => {
|
|
102
|
+
const next = new Set(prev);
|
|
103
|
+
if (next.has(k))
|
|
104
|
+
next.delete(k);
|
|
105
|
+
else
|
|
106
|
+
next.add(k);
|
|
107
|
+
return next;
|
|
108
|
+
});
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (input === 'a') {
|
|
112
|
+
// Select / deselect all
|
|
113
|
+
setSelected(prev => prev.size === allSources.length
|
|
114
|
+
? new Set()
|
|
115
|
+
: new Set(allSources.map(s => `${s.app}/${s.container}`)));
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (input === 'p') {
|
|
120
|
+
setPaused(p => !p);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
if (input === 'c') {
|
|
124
|
+
setLines([]);
|
|
125
|
+
pendingRef.current = [];
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (input === 'L') {
|
|
129
|
+
// Cycle level filter
|
|
130
|
+
setLevel(l => LEVEL_RANKS[(LEVEL_RANKS.indexOf(l) + 1) % LEVEL_RANKS.length]);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
};
|
|
135
|
+
useRegisterHandler(handler);
|
|
136
|
+
// Apply level filter at render time so the buffer keeps everything (cheap to
|
|
137
|
+
// change filter back and forth without losing history).
|
|
138
|
+
const filteredLines = useMemo(() => {
|
|
139
|
+
if (level === 'all')
|
|
140
|
+
return lines;
|
|
141
|
+
const minRank = LEVEL_RANK_NUMBER[level];
|
|
142
|
+
return lines.filter(l => {
|
|
143
|
+
if (l.level === 'unknown')
|
|
144
|
+
return false;
|
|
145
|
+
return LEVEL_RANK_NUMBER[l.level] >= minRank;
|
|
146
|
+
});
|
|
147
|
+
}, [lines, level]);
|
|
148
|
+
// Rough split of the viewport: 30% picker, 70% logs (min 5 rows each).
|
|
149
|
+
const totalH = Math.max(10, availableHeight - 4);
|
|
150
|
+
const pickerH = Math.max(5, Math.floor(totalH * 0.3));
|
|
151
|
+
const logsH = Math.max(5, totalH - pickerH - 1);
|
|
152
|
+
const visibleLogs = filteredLines.slice(-logsH);
|
|
153
|
+
const maxLabelLen = useMemo(() => allSources.reduce((m, s) => Math.max(m, `${s.app}/${s.container}`.length), 0), [allSources]);
|
|
154
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, gap: 2, children: [_jsx(Text, { bold: true, color: colors.primary, children: "Multi-source Logs" }), _jsxs(Text, { color: colors.muted, children: [selected.size, "/", allSources.length, " sources \u00B7 level:", level, " \u00B7 ", paused ? 'PAUSED' : 'live'] }), !paused && handleRef.current && handleRef.current.active() > 0 && (_jsxs(Text, { color: colors.success, children: [_jsx(Spinner, { type: "dots" }), " tailing"] }))] }), _jsxs(Box, { flexDirection: "column", height: pickerH, marginBottom: 1, borderStyle: pickerFocused ? 'round' : 'single', borderColor: pickerFocused ? colors.primary : colors.muted, children: [_jsx(Box, { paddingX: 1, children: _jsx(Text, { bold: true, color: colors.muted, children: "Sources [Tab to switch focus, Space toggle, a all/none]" }) }), _jsxs(Box, { flexDirection: "column", paddingX: 1, children: [allSources.slice(0, pickerH - 2).map((src, i) => {
|
|
155
|
+
const k = `${src.app}/${src.container}`;
|
|
156
|
+
const checked = selected.has(k);
|
|
157
|
+
const cursor = pickerFocused && i === pickerIndex ? '>' : ' ';
|
|
158
|
+
return (_jsxs(Text, { color: checked ? colors.success : colors.muted, children: [cursor, " ", checked ? '☑' : '☐', " ", redact(src.app), "/", src.container] }, k));
|
|
159
|
+
}), allSources.length > pickerH - 2 && (_jsxs(Text, { color: colors.muted, children: ["\u2026 ", allSources.length - (pickerH - 2), " more"] }))] })] }), _jsxs(Box, { flexDirection: "column", height: logsH, borderStyle: !pickerFocused ? 'round' : 'single', borderColor: !pickerFocused ? colors.primary : colors.muted, children: [_jsx(Box, { paddingX: 1, children: _jsxs(Text, { bold: true, color: colors.muted, children: ["Logs [p pause \u00B7 c clear \u00B7 L level cycle \u00B7 last ", visibleLogs.length, "/", filteredLines.length, "]"] }) }), _jsx(Box, { flexDirection: "column", paddingX: 1, children: visibleLogs.map(line => {
|
|
160
|
+
const label = `${line.app}/${line.container}`.padEnd(maxLabelLen);
|
|
161
|
+
const colour = colourForSource(`${line.app}/${line.container}`);
|
|
162
|
+
const ts = line.ts.toISOString().slice(11, 19); // HH:MM:SS
|
|
163
|
+
return (_jsxs(Text, { wrap: "truncate", children: [_jsxs(Text, { color: colors.muted, children: [ts, " "] }), _jsx(Text, { color: colour, children: label }), _jsxs(Text, { children: [" ", line.text] })] }, line.id));
|
|
164
|
+
}) })] })] }));
|
|
165
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
3
|
-
import { Box, Text
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
|
+
import { useRegisterHandler } from '@matthesketh/ink-input-dispatcher';
|
|
5
6
|
import { useAppState, useAppDispatch } from '../state.js';
|
|
6
7
|
import { useSecrets } from '../hooks/use-secrets.js';
|
|
7
8
|
import { getSecret as getCoreSecret } from '../../core/secrets-ops.js';
|
|
@@ -15,6 +16,13 @@ export function SecretEdit() {
|
|
|
15
16
|
const [value, setValue] = useState('');
|
|
16
17
|
const [phase, setPhase] = useState(isNew ? 'key' : 'value');
|
|
17
18
|
const [status, setStatus] = useState(null);
|
|
19
|
+
const timerRef = useRef(null);
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
return () => {
|
|
22
|
+
if (timerRef.current)
|
|
23
|
+
clearTimeout(timerRef.current);
|
|
24
|
+
};
|
|
25
|
+
}, []);
|
|
18
26
|
useEffect(() => {
|
|
19
27
|
if (!isNew && selectedApp && selectedSecret) {
|
|
20
28
|
try {
|
|
@@ -26,14 +34,14 @@ export function SecretEdit() {
|
|
|
26
34
|
// ignore
|
|
27
35
|
}
|
|
28
36
|
}
|
|
29
|
-
}, []);
|
|
37
|
+
}, [isNew, selectedApp, selectedSecret]);
|
|
30
38
|
const save = () => {
|
|
31
39
|
if (!selectedApp || !keyName)
|
|
32
40
|
return;
|
|
33
41
|
const result = secrets.saveSecret(selectedApp, keyName, value);
|
|
34
42
|
if (result.ok) {
|
|
35
43
|
setStatus('Saved and re-sealed');
|
|
36
|
-
setTimeout(() => {
|
|
44
|
+
timerRef.current = setTimeout(() => {
|
|
37
45
|
dispatch({ type: 'GO_BACK' });
|
|
38
46
|
}, 500);
|
|
39
47
|
}
|
|
@@ -41,13 +49,16 @@ export function SecretEdit() {
|
|
|
41
49
|
setStatus(`Error: ${result.error}`);
|
|
42
50
|
}
|
|
43
51
|
};
|
|
44
|
-
|
|
52
|
+
const handler = (_input, key) => {
|
|
45
53
|
if (key.escape) {
|
|
46
54
|
dispatch({ type: 'GO_BACK' });
|
|
55
|
+
return true;
|
|
47
56
|
}
|
|
48
|
-
|
|
57
|
+
return false;
|
|
58
|
+
};
|
|
59
|
+
useRegisterHandler(handler);
|
|
49
60
|
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Text, { bold: true, color: colors.primary, children: [isNew ? 'Add Secret' : 'Edit Secret', " - ", selectedApp] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", gap: 1, children: [_jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Key: " }), isNew && phase === 'key' ? (_jsx(TextInput, { value: keyName, onChange: setKeyName, onSubmit: () => {
|
|
50
61
|
if (keyName)
|
|
51
62
|
setPhase('value');
|
|
52
|
-
} })) : (_jsx(Text, { bold: true, children: keyName }))] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Value: " }), phase === 'value' ? (_jsx(TextInput, { value: value, onChange: setValue, onSubmit: save })) : (_jsx(Text, { color: colors.muted, children: "(press Enter on key first)" }))] })] }), status && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: status.startsWith('Error') ? colors.error : colors.success, children: status }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, children: "Enter to save | Esc to cancel" }) })] }));
|
|
63
|
+
} })) : (_jsx(Text, { bold: true, children: keyName }))] }), _jsxs(Box, { children: [_jsx(Text, { color: colors.muted, children: "Value: " }), phase === 'value' ? (_jsx(TextInput, { value: value, onChange: setValue, onSubmit: save, mask: "*" })) : (_jsx(Text, { color: colors.muted, children: "(press Enter on key first)" }))] })] }), status && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: status.startsWith('Error') ? colors.error : colors.success, children: status }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: colors.muted, children: "Enter to save | Esc to cancel" }) })] }));
|
|
53
64
|
}
|
|
@@ -1,82 +1,97 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect,
|
|
3
|
-
import { Box, Text
|
|
2
|
+
import { useEffect, useCallback } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { useRegisterHandler } from '@matthesketh/ink-input-dispatcher';
|
|
5
|
+
import { ScrollableList } from '@matthesketh/ink-scrollable-list';
|
|
6
|
+
import { useAvailableHeight } from '@matthesketh/ink-viewport';
|
|
4
7
|
import { useAppState, useAppDispatch, useRedact } from '../state.js';
|
|
5
8
|
import { useSecrets } from '../hooks/use-secrets.js';
|
|
6
9
|
import { colors } from '../theme.js';
|
|
7
10
|
export function SecretsView() {
|
|
8
|
-
const
|
|
11
|
+
const state = useAppState();
|
|
9
12
|
const dispatch = useAppDispatch();
|
|
10
13
|
const redact = useRedact();
|
|
11
14
|
const secrets = useSecrets();
|
|
12
|
-
const
|
|
13
|
-
const
|
|
15
|
+
const availableHeight = useAvailableHeight();
|
|
16
|
+
const { secretsSubView: subView, secretsIndex: selectedIndex, selectedApp } = state;
|
|
17
|
+
const refresh = secrets.refresh;
|
|
14
18
|
useEffect(() => {
|
|
15
|
-
|
|
16
|
-
}, []);
|
|
19
|
+
refresh();
|
|
20
|
+
}, [refresh]);
|
|
17
21
|
useEffect(() => {
|
|
18
22
|
if (subView === 'secret-list' && selectedApp) {
|
|
19
23
|
secrets.loadAppSecrets(selectedApp);
|
|
20
24
|
}
|
|
21
|
-
}, [subView, selectedApp]);
|
|
22
|
-
|
|
25
|
+
}, [subView, selectedApp, secrets.loadAppSecrets]);
|
|
26
|
+
const handler = useCallback((input, key) => {
|
|
23
27
|
if (subView === 'app-list') {
|
|
24
28
|
if (input === 'j' || key.downArrow) {
|
|
25
|
-
|
|
29
|
+
dispatch({ type: 'SET_INDEX', view: 'secrets', index: Math.min(selectedIndex + 1, secrets.apps.length - 1) });
|
|
30
|
+
return true;
|
|
26
31
|
}
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
if (input === 'k' || key.upArrow) {
|
|
33
|
+
dispatch({ type: 'SET_INDEX', view: 'secrets', index: Math.max(selectedIndex - 1, 0) });
|
|
34
|
+
return true;
|
|
29
35
|
}
|
|
30
|
-
|
|
36
|
+
if (key.return && secrets.apps[selectedIndex]) {
|
|
31
37
|
dispatch({ type: 'SELECT_APP', app: secrets.apps[selectedIndex].app });
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
dispatch({ type: 'SET_SECRETS_SUBVIEW', subView: 'secret-list' });
|
|
39
|
+
return true;
|
|
34
40
|
}
|
|
35
|
-
|
|
41
|
+
if (input === 'u') {
|
|
36
42
|
const result = secrets.unseal();
|
|
37
43
|
if (!result.ok) {
|
|
38
44
|
dispatch({ type: 'SET_ERROR', error: result.error ?? 'Unseal failed' });
|
|
39
45
|
}
|
|
40
46
|
secrets.refresh();
|
|
47
|
+
return true;
|
|
41
48
|
}
|
|
42
|
-
|
|
49
|
+
if (input === 'l') {
|
|
43
50
|
const result = secrets.seal();
|
|
44
51
|
if (!result.ok) {
|
|
45
52
|
dispatch({ type: 'SET_ERROR', error: result.error ?? 'Seal failed' });
|
|
46
53
|
}
|
|
47
54
|
secrets.refresh();
|
|
55
|
+
return true;
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
else if (subView === 'secret-list') {
|
|
59
|
+
if (secrets.secrets.length === 0)
|
|
60
|
+
return false;
|
|
51
61
|
if (input === 'j' || key.downArrow) {
|
|
52
|
-
|
|
62
|
+
dispatch({ type: 'SET_INDEX', view: 'secrets', index: Math.min(selectedIndex + 1, secrets.secrets.length - 1) });
|
|
63
|
+
return true;
|
|
53
64
|
}
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
if (input === 'k' || key.upArrow) {
|
|
66
|
+
dispatch({ type: 'SET_INDEX', view: 'secrets', index: Math.max(selectedIndex - 1, 0) });
|
|
67
|
+
return true;
|
|
56
68
|
}
|
|
57
|
-
|
|
69
|
+
if (key.return && secrets.secrets[selectedIndex] && selectedApp) {
|
|
58
70
|
dispatch({ type: 'SELECT_SECRET', key: secrets.secrets[selectedIndex].key });
|
|
59
71
|
dispatch({ type: 'NAVIGATE', view: 'secret-edit' });
|
|
72
|
+
return true;
|
|
60
73
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
if (key.escape) {
|
|
75
|
+
dispatch({ type: 'SET_SECRETS_SUBVIEW', subView: 'app-list' });
|
|
76
|
+
return true;
|
|
64
77
|
}
|
|
65
|
-
|
|
78
|
+
if (input === 'a' && selectedApp) {
|
|
66
79
|
dispatch({ type: 'SELECT_SECRET', key: null });
|
|
67
80
|
dispatch({ type: 'NAVIGATE', view: 'secret-edit' });
|
|
81
|
+
return true;
|
|
68
82
|
}
|
|
69
|
-
|
|
83
|
+
if (input === 'd' && selectedApp && secrets.secrets[selectedIndex]) {
|
|
70
84
|
const secretKey = secrets.secrets[selectedIndex].key;
|
|
85
|
+
const appName = selectedApp;
|
|
71
86
|
dispatch({
|
|
72
87
|
type: 'CONFIRM',
|
|
73
88
|
action: {
|
|
74
89
|
label: `Delete secret "${secretKey}"?`,
|
|
75
|
-
description: `This will remove ${secretKey} from ${redact(
|
|
90
|
+
description: `This will remove ${secretKey} from ${redact(appName)}'s vault.`,
|
|
76
91
|
onConfirm: () => {
|
|
77
|
-
const result = secrets.deleteSecret(
|
|
92
|
+
const result = secrets.deleteSecret(appName, secretKey);
|
|
78
93
|
if (result.ok) {
|
|
79
|
-
secrets.loadAppSecrets(
|
|
94
|
+
secrets.loadAppSecrets(appName);
|
|
80
95
|
secrets.refresh();
|
|
81
96
|
}
|
|
82
97
|
else {
|
|
@@ -85,8 +100,9 @@ export function SecretsView() {
|
|
|
85
100
|
},
|
|
86
101
|
},
|
|
87
102
|
});
|
|
103
|
+
return true;
|
|
88
104
|
}
|
|
89
|
-
|
|
105
|
+
if (input === 'r' && selectedApp && secrets.secrets[selectedIndex]) {
|
|
90
106
|
const secretKey = secrets.secrets[selectedIndex].key;
|
|
91
107
|
if (secrets.revealedValues[secretKey]) {
|
|
92
108
|
secrets.hideSecret(secretKey);
|
|
@@ -94,15 +110,15 @@ export function SecretsView() {
|
|
|
94
110
|
else {
|
|
95
111
|
secrets.revealSecret(selectedApp, secretKey);
|
|
96
112
|
}
|
|
113
|
+
return true;
|
|
97
114
|
}
|
|
98
115
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
})) })] }))] }));
|
|
116
|
+
return false;
|
|
117
|
+
}, [subView, selectedIndex, selectedApp, secrets, dispatch, redact]);
|
|
118
|
+
useRegisterHandler(handler);
|
|
119
|
+
const listHeight = Math.max(5, availableHeight - 5);
|
|
120
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsxs(Box, { marginBottom: 1, paddingX: 1, gap: 2, children: [_jsx(Text, { bold: true, children: "Vault:" }), !secrets.initialized ? (_jsx(Text, { color: colors.error, children: "Not initialized" })) : secrets.sealed ? (_jsx(Text, { color: colors.warning, bold: true, children: "SEALED" })) : (_jsx(Text, { color: colors.success, bold: true, children: "UNSEALED" })), _jsxs(Text, { color: colors.muted, children: [secrets.apps.length, " apps | ", secrets.apps.reduce((sum, a) => sum + a.keyCount, 0), " keys"] })] }), secrets.error && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.error, children: secrets.error }) })), subView === 'app-list' ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Apps with secrets:" }), _jsx(ScrollableList, { items: secrets.apps, selectedIndex: Math.min(selectedIndex, secrets.apps.length - 1), maxVisible: listHeight, emptyText: " No secrets managed", renderItem: (app, selected) => (_jsxs(Box, { children: [_jsx(Text, { color: colors.primary, children: selected ? '> ' : ' ' }), _jsx(Text, { bold: selected, color: selected ? colors.primary : colors.text, children: redact(app.app).padEnd(24) }), _jsx(Text, { color: colors.muted, children: app.type.padEnd(14) }), _jsxs(Text, { children: [String(app.keyCount).padEnd(8), " keys"] })] })) })] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: colors.primary, children: redact(selectedApp ?? '') }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: _jsx(ScrollableList, { items: secrets.secrets, selectedIndex: Math.min(selectedIndex, secrets.secrets.length - 1), maxVisible: listHeight, emptyText: " No secrets found", renderItem: (secret, selected) => {
|
|
121
|
+
const revealed = secrets.revealedValues[secret.key];
|
|
122
|
+
return (_jsxs(Box, { children: [_jsx(Text, { color: colors.primary, children: selected ? '> ' : ' ' }), _jsx(Text, { bold: selected, color: selected ? colors.primary : colors.text, children: secret.key.padEnd(30) }), _jsx(Text, { color: revealed ? colors.warning : colors.muted, children: revealed ?? secret.maskedValue })] }));
|
|
123
|
+
} }) })] }))] }));
|
|
108
124
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain-text and hidden-input prompts, no external deps.
|
|
3
|
+
*
|
|
4
|
+
* promptHidden uses raw mode + manual char-by-char read so the echoed value
|
|
5
|
+
* never appears on the terminal — important for pasting secrets and for
|
|
6
|
+
* tools like `script` / asciinema.
|
|
7
|
+
*/
|
|
8
|
+
export declare function prompt(message: string, defaultValue?: string): Promise<string>;
|
|
9
|
+
/**
|
|
10
|
+
* String variant — convenience wrapper around the Buffer variant. Use the
|
|
11
|
+
* Buffer variant directly if you want the strongest in-memory guarantees;
|
|
12
|
+
* this one converts to string and the result lives in the V8 heap until GC.
|
|
13
|
+
*/
|
|
14
|
+
export declare function promptHidden(message: string): Promise<string>;
|
|
15
|
+
/**
|
|
16
|
+
* Buffer-based hidden input. Returns the raw input bytes in a Buffer the
|
|
17
|
+
* caller is expected to zero out (`buf.fill(0)`) when finished — see
|
|
18
|
+
* `withSecretBuffer` for an automated pattern.
|
|
19
|
+
*
|
|
20
|
+
* Why Buffer (not string)?
|
|
21
|
+
* - Node strings are immutable + interned in V8 heap; you can't zero them.
|
|
22
|
+
* Once a secret string exists, it sits in the heap until GC.
|
|
23
|
+
* - Buffer is a writable byte array. Calling `buf.fill(0)` overwrites the
|
|
24
|
+
* bytes in-place; subsequent heap dumps and core dumps contain zeros.
|
|
25
|
+
*
|
|
26
|
+
* Hardening:
|
|
27
|
+
* - Buffer is grown by `Buffer.concat` and the intermediate buffers are
|
|
28
|
+
* zeroed before being released.
|
|
29
|
+
* - Non-TTY fallback uses `terminal: false` so readline can never promote
|
|
30
|
+
* stdout to terminal mode and echo the value.
|
|
31
|
+
* - End/error/SIGINT all reject + restore terminal state and zero the
|
|
32
|
+
* in-flight buffer so a death never leaves bytes behind.
|
|
33
|
+
*
|
|
34
|
+
* KNOWN LIMITATION: any string copy made downstream (e.g. `buf.toString()`
|
|
35
|
+
* for regex validation) lives in V8 heap until GC. Convert as late as
|
|
36
|
+
* possible and let the string go out of scope ASAP.
|
|
37
|
+
*/
|
|
38
|
+
export declare function promptHiddenBuffer(message: string): Promise<Buffer>;
|
|
39
|
+
/**
|
|
40
|
+
* Recommended pattern for handling a secret in memory: read into a Buffer,
|
|
41
|
+
* pass to your callback, zero on exit (success or throw). Use this instead
|
|
42
|
+
* of `promptHidden` when the value will be processed by code under your
|
|
43
|
+
* control end-to-end.
|
|
44
|
+
*
|
|
45
|
+
* Example:
|
|
46
|
+
* await withSecretBuffer('Paste new STRIPE_SECRET_KEY', async (buf) => {
|
|
47
|
+
* const asString = buf.toString('utf8'); // brief string copy
|
|
48
|
+
* await sealApp(app, applyRotation(...)); // age-encrypt happens here
|
|
49
|
+
* // String copy is unreferenced from this point; buf is zeroed on exit.
|
|
50
|
+
* });
|
|
51
|
+
*/
|
|
52
|
+
export declare function withSecretBuffer<T>(message: string, fn: (value: Buffer) => Promise<T>): Promise<T>;
|