@matthesketh/fleet 1.2.0 → 1.7.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 +46 -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/guard.d.ts +1 -0
- package/dist/commands/guard.js +144 -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/KeyHint.js +10 -0
- 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/router.js +60 -7
- 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 +1 -1
- package/dist/tui/tests/keyboard-integration.test.js +3 -0
- package/dist/tui/tests/test-app.js +1 -1
- package/dist/tui/types.d.ts +2 -2
- package/dist/tui/views/AppDetail.js +3 -4
- package/dist/tui/views/HealthView.js +7 -1
- package/dist/tui/views/LogsView.js +24 -1
- package/dist/tui/views/MultiLogsView.d.ts +2 -0
- package/dist/tui/views/MultiLogsView.js +165 -0
- package/dist/tui/views/SecretEdit.js +10 -3
- package/dist/tui/views/SecretsView.js +6 -3
- package/dist/ui/prompt.d.ts +52 -0
- package/dist/ui/prompt.js +169 -0
- package/package.json +34 -21
- package/scripts/guard/cert-expiry-watch +109 -0
- package/scripts/guard/cf-audit-monitor +169 -0
- package/scripts/guard/cf-snapshot +124 -0
- package/scripts/guard/cron.d-cf-protect +11 -0
- package/scripts/guard/dns-drift-watch +138 -0
- package/scripts/guard/fleet-guard +282 -0
- package/scripts/guard/fleet-guard-execute +197 -0
- package/scripts/guard/notify +108 -0
- 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
|
@@ -17,6 +17,8 @@ export function LogsView() {
|
|
|
17
17
|
const [following, setFollowing] = useState(false);
|
|
18
18
|
const [loading, setLoading] = useState(true);
|
|
19
19
|
const streamRef = useRef(null);
|
|
20
|
+
const lineBufferRef = useRef([]);
|
|
21
|
+
const flushTimerRef = useRef(null);
|
|
20
22
|
useEffect(() => {
|
|
21
23
|
if (!selectedApp)
|
|
22
24
|
return;
|
|
@@ -35,6 +37,10 @@ export function LogsView() {
|
|
|
35
37
|
streamRef.current.kill();
|
|
36
38
|
streamRef.current = null;
|
|
37
39
|
}
|
|
40
|
+
if (flushTimerRef.current) {
|
|
41
|
+
clearInterval(flushTimerRef.current);
|
|
42
|
+
flushTimerRef.current = null;
|
|
43
|
+
}
|
|
38
44
|
};
|
|
39
45
|
}, [selectedApp]);
|
|
40
46
|
const handler = (input, key) => {
|
|
@@ -44,6 +50,16 @@ export function LogsView() {
|
|
|
44
50
|
streamRef.current.kill();
|
|
45
51
|
streamRef.current = null;
|
|
46
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
|
+
}
|
|
47
63
|
setFollowing(false);
|
|
48
64
|
}
|
|
49
65
|
else if (selectedApp) {
|
|
@@ -51,8 +67,15 @@ export function LogsView() {
|
|
|
51
67
|
const handle = streamFleetCommand(['logs', selectedApp, '-f']);
|
|
52
68
|
streamRef.current = handle;
|
|
53
69
|
handle.onData((line) => {
|
|
54
|
-
|
|
70
|
+
lineBufferRef.current.push(line);
|
|
55
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);
|
|
56
79
|
}
|
|
57
80
|
return true;
|
|
58
81
|
}
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useState, useEffect, useRef } from 'react';
|
|
3
3
|
import { Box, Text } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
5
|
import { useRegisterHandler } from '@matthesketh/ink-input-dispatcher';
|
|
@@ -16,6 +16,13 @@ export function SecretEdit() {
|
|
|
16
16
|
const [value, setValue] = useState('');
|
|
17
17
|
const [phase, setPhase] = useState(isNew ? 'key' : 'value');
|
|
18
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
|
+
}, []);
|
|
19
26
|
useEffect(() => {
|
|
20
27
|
if (!isNew && selectedApp && selectedSecret) {
|
|
21
28
|
try {
|
|
@@ -34,7 +41,7 @@ export function SecretEdit() {
|
|
|
34
41
|
const result = secrets.saveSecret(selectedApp, keyName, value);
|
|
35
42
|
if (result.ok) {
|
|
36
43
|
setStatus('Saved and re-sealed');
|
|
37
|
-
setTimeout(() => {
|
|
44
|
+
timerRef.current = setTimeout(() => {
|
|
38
45
|
dispatch({ type: 'GO_BACK' });
|
|
39
46
|
}, 500);
|
|
40
47
|
}
|
|
@@ -53,5 +60,5 @@ export function SecretEdit() {
|
|
|
53
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: () => {
|
|
54
61
|
if (keyName)
|
|
55
62
|
setPhase('value');
|
|
56
|
-
} })) : (_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" }) })] }));
|
|
57
64
|
}
|
|
@@ -56,6 +56,8 @@ export function SecretsView() {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
else if (subView === 'secret-list') {
|
|
59
|
+
if (secrets.secrets.length === 0)
|
|
60
|
+
return false;
|
|
59
61
|
if (input === 'j' || key.downArrow) {
|
|
60
62
|
dispatch({ type: 'SET_INDEX', view: 'secrets', index: Math.min(selectedIndex + 1, secrets.secrets.length - 1) });
|
|
61
63
|
return true;
|
|
@@ -80,15 +82,16 @@ export function SecretsView() {
|
|
|
80
82
|
}
|
|
81
83
|
if (input === 'd' && selectedApp && secrets.secrets[selectedIndex]) {
|
|
82
84
|
const secretKey = secrets.secrets[selectedIndex].key;
|
|
85
|
+
const appName = selectedApp;
|
|
83
86
|
dispatch({
|
|
84
87
|
type: 'CONFIRM',
|
|
85
88
|
action: {
|
|
86
89
|
label: `Delete secret "${secretKey}"?`,
|
|
87
|
-
description: `This will remove ${secretKey} from ${redact(
|
|
90
|
+
description: `This will remove ${secretKey} from ${redact(appName)}'s vault.`,
|
|
88
91
|
onConfirm: () => {
|
|
89
|
-
const result = secrets.deleteSecret(
|
|
92
|
+
const result = secrets.deleteSecret(appName, secretKey);
|
|
90
93
|
if (result.ok) {
|
|
91
|
-
secrets.loadAppSecrets(
|
|
94
|
+
secrets.loadAppSecrets(appName);
|
|
92
95
|
secrets.refresh();
|
|
93
96
|
}
|
|
94
97
|
else {
|
|
@@ -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>;
|
|
@@ -0,0 +1,169 @@
|
|
|
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
|
+
import * as readline from 'node:readline';
|
|
9
|
+
export async function prompt(message, defaultValue) {
|
|
10
|
+
const hint = defaultValue !== undefined ? ` [${defaultValue}]` : '';
|
|
11
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
12
|
+
return new Promise(resolve => {
|
|
13
|
+
rl.question(`${message}${hint}: `, answer => {
|
|
14
|
+
rl.close();
|
|
15
|
+
const trimmed = answer.trim();
|
|
16
|
+
resolve(trimmed === '' && defaultValue !== undefined ? defaultValue : trimmed);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* String variant — convenience wrapper around the Buffer variant. Use the
|
|
22
|
+
* Buffer variant directly if you want the strongest in-memory guarantees;
|
|
23
|
+
* this one converts to string and the result lives in the V8 heap until GC.
|
|
24
|
+
*/
|
|
25
|
+
export async function promptHidden(message) {
|
|
26
|
+
const buf = await promptHiddenBuffer(message);
|
|
27
|
+
try {
|
|
28
|
+
return buf.toString('utf8');
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
buf.fill(0);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Buffer-based hidden input. Returns the raw input bytes in a Buffer the
|
|
36
|
+
* caller is expected to zero out (`buf.fill(0)`) when finished — see
|
|
37
|
+
* `withSecretBuffer` for an automated pattern.
|
|
38
|
+
*
|
|
39
|
+
* Why Buffer (not string)?
|
|
40
|
+
* - Node strings are immutable + interned in V8 heap; you can't zero them.
|
|
41
|
+
* Once a secret string exists, it sits in the heap until GC.
|
|
42
|
+
* - Buffer is a writable byte array. Calling `buf.fill(0)` overwrites the
|
|
43
|
+
* bytes in-place; subsequent heap dumps and core dumps contain zeros.
|
|
44
|
+
*
|
|
45
|
+
* Hardening:
|
|
46
|
+
* - Buffer is grown by `Buffer.concat` and the intermediate buffers are
|
|
47
|
+
* zeroed before being released.
|
|
48
|
+
* - Non-TTY fallback uses `terminal: false` so readline can never promote
|
|
49
|
+
* stdout to terminal mode and echo the value.
|
|
50
|
+
* - End/error/SIGINT all reject + restore terminal state and zero the
|
|
51
|
+
* in-flight buffer so a death never leaves bytes behind.
|
|
52
|
+
*
|
|
53
|
+
* KNOWN LIMITATION: any string copy made downstream (e.g. `buf.toString()`
|
|
54
|
+
* for regex validation) lives in V8 heap until GC. Convert as late as
|
|
55
|
+
* possible and let the string go out of scope ASAP.
|
|
56
|
+
*/
|
|
57
|
+
export function promptHiddenBuffer(message) {
|
|
58
|
+
if (!process.stdin.isTTY) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
const rl = readline.createInterface({ input: process.stdin, terminal: false });
|
|
61
|
+
process.stdout.write(message + ': ');
|
|
62
|
+
let done = false;
|
|
63
|
+
const finish = (cb) => { if (done)
|
|
64
|
+
return; done = true; rl.close(); cb(); };
|
|
65
|
+
rl.once('line', line => finish(() => resolve(Buffer.from(line, 'utf8'))));
|
|
66
|
+
rl.once('close', () => finish(() => reject(new Error('Cancelled (stdin closed)'))));
|
|
67
|
+
rl.once('error', err => finish(() => reject(err)));
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const stdin = process.stdin;
|
|
72
|
+
process.stdout.write(message + ': ');
|
|
73
|
+
// Pre-allocate; grow geometrically to avoid quadratic Buffer.concat.
|
|
74
|
+
let buf = Buffer.alloc(64);
|
|
75
|
+
let len = 0;
|
|
76
|
+
let settled = false;
|
|
77
|
+
const wasRaw = stdin.isRaw;
|
|
78
|
+
const grow = (need) => {
|
|
79
|
+
if (len + need <= buf.length)
|
|
80
|
+
return;
|
|
81
|
+
const next = Buffer.alloc(Math.max(buf.length * 2, len + need));
|
|
82
|
+
buf.copy(next);
|
|
83
|
+
buf.fill(0); // zero the old buffer before releasing
|
|
84
|
+
buf = next;
|
|
85
|
+
};
|
|
86
|
+
const cleanup = () => {
|
|
87
|
+
stdin.removeListener('data', onData);
|
|
88
|
+
stdin.removeListener('end', onEnd);
|
|
89
|
+
stdin.removeListener('error', onError);
|
|
90
|
+
process.removeListener('SIGINT', onSigint);
|
|
91
|
+
try {
|
|
92
|
+
stdin.setRawMode(wasRaw);
|
|
93
|
+
}
|
|
94
|
+
catch { /* terminal may be gone */ }
|
|
95
|
+
stdin.pause();
|
|
96
|
+
};
|
|
97
|
+
const settle = (fn) => {
|
|
98
|
+
if (settled)
|
|
99
|
+
return;
|
|
100
|
+
settled = true;
|
|
101
|
+
cleanup();
|
|
102
|
+
fn();
|
|
103
|
+
};
|
|
104
|
+
const wipeAndReject = (err) => {
|
|
105
|
+
buf.fill(0);
|
|
106
|
+
settle(() => reject(err));
|
|
107
|
+
};
|
|
108
|
+
const onData = (chunk) => {
|
|
109
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
110
|
+
const c = chunk[i];
|
|
111
|
+
if (c === 0x0d || c === 0x0a) { // \r \n
|
|
112
|
+
process.stdout.write('\n');
|
|
113
|
+
const out = Buffer.alloc(len);
|
|
114
|
+
buf.copy(out, 0, 0, len);
|
|
115
|
+
buf.fill(0);
|
|
116
|
+
settle(() => resolve(out));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
else if (c === 0x03) { // Ctrl-C
|
|
120
|
+
process.stdout.write('\n');
|
|
121
|
+
wipeAndReject(new Error('Cancelled'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
else if (c === 0x7f || c === 0x08) { // backspace
|
|
125
|
+
if (len > 0) {
|
|
126
|
+
len -= 1;
|
|
127
|
+
buf[len] = 0;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else if (c >= 0x20) { // printable
|
|
131
|
+
grow(1);
|
|
132
|
+
buf[len++] = c;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
const onEnd = () => wipeAndReject(new Error('Cancelled (stdin ended)'));
|
|
137
|
+
const onError = (err) => wipeAndReject(err);
|
|
138
|
+
const onSigint = () => { process.stdout.write('\n'); wipeAndReject(new Error('Cancelled (SIGINT)')); };
|
|
139
|
+
stdin.setRawMode(true);
|
|
140
|
+
stdin.resume();
|
|
141
|
+
// Note: do NOT setEncoding — we want raw bytes, not auto-decoded strings.
|
|
142
|
+
stdin.on('data', onData);
|
|
143
|
+
stdin.on('end', onEnd);
|
|
144
|
+
stdin.on('error', onError);
|
|
145
|
+
process.on('SIGINT', onSigint);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Recommended pattern for handling a secret in memory: read into a Buffer,
|
|
150
|
+
* pass to your callback, zero on exit (success or throw). Use this instead
|
|
151
|
+
* of `promptHidden` when the value will be processed by code under your
|
|
152
|
+
* control end-to-end.
|
|
153
|
+
*
|
|
154
|
+
* Example:
|
|
155
|
+
* await withSecretBuffer('Paste new STRIPE_SECRET_KEY', async (buf) => {
|
|
156
|
+
* const asString = buf.toString('utf8'); // brief string copy
|
|
157
|
+
* await sealApp(app, applyRotation(...)); // age-encrypt happens here
|
|
158
|
+
* // String copy is unreferenced from this point; buf is zeroed on exit.
|
|
159
|
+
* });
|
|
160
|
+
*/
|
|
161
|
+
export async function withSecretBuffer(message, fn) {
|
|
162
|
+
const buf = await promptHiddenBuffer(message);
|
|
163
|
+
try {
|
|
164
|
+
return await fn(buf);
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
buf.fill(0);
|
|
168
|
+
}
|
|
169
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matthesketh/fleet",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "Docker production management CLI + MCP server for Claude Code",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist/",
|
|
12
12
|
"data/registry.example.json",
|
|
13
|
+
"scripts/guard/",
|
|
13
14
|
"LICENSE",
|
|
14
15
|
"README.md"
|
|
15
16
|
],
|
|
16
|
-
"workspaces": ["packages/*"],
|
|
17
17
|
"scripts": {
|
|
18
|
-
"build": "tsc",
|
|
18
|
+
"build": "tsc && tsc-alias",
|
|
19
19
|
"dev": "tsx src/index.ts",
|
|
20
20
|
"test": "vitest run",
|
|
21
21
|
"prepublishOnly": "npm run build"
|
|
@@ -48,33 +48,46 @@
|
|
|
48
48
|
"node": ">=20"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@
|
|
52
|
-
"@matthesketh/ink-
|
|
53
|
-
"@matthesketh/ink-
|
|
54
|
-
"@matthesketh/ink-
|
|
55
|
-
"@matthesketh/ink-
|
|
56
|
-
"@matthesketh/ink-
|
|
57
|
-
"@matthesketh/ink-
|
|
58
|
-
"@matthesketh/ink-
|
|
59
|
-
"@matthesketh/ink-
|
|
60
|
-
"@matthesketh/ink-
|
|
61
|
-
"@matthesketh/ink-
|
|
62
|
-
"@matthesketh/ink-
|
|
63
|
-
"@matthesketh/ink-
|
|
64
|
-
"@matthesketh/ink-
|
|
65
|
-
"@matthesketh/ink-
|
|
66
|
-
"@matthesketh/ink-
|
|
51
|
+
"@matthesketh/ink-breadcrumb": "^0.1.0",
|
|
52
|
+
"@matthesketh/ink-chart": "0.1.0",
|
|
53
|
+
"@matthesketh/ink-form": "0.1.0",
|
|
54
|
+
"@matthesketh/ink-fuzzy-select": "0.1.0",
|
|
55
|
+
"@matthesketh/ink-gauge": "^0.1.0",
|
|
56
|
+
"@matthesketh/ink-input-dispatcher": "^0.1.0",
|
|
57
|
+
"@matthesketh/ink-keybinding-help": "^0.1.0",
|
|
58
|
+
"@matthesketh/ink-log-viewer": "^0.1.0",
|
|
59
|
+
"@matthesketh/ink-modal": "^0.1.0",
|
|
60
|
+
"@matthesketh/ink-pipeline": "^0.1.0",
|
|
61
|
+
"@matthesketh/ink-rule": "^0.1.0",
|
|
62
|
+
"@matthesketh/ink-scrollable-list": "^0.1.1",
|
|
63
|
+
"@matthesketh/ink-split-pane": "^0.1.0",
|
|
64
|
+
"@matthesketh/ink-stable-state": "^0.1.0",
|
|
65
|
+
"@matthesketh/ink-status-bar": "^0.1.0",
|
|
66
|
+
"@matthesketh/ink-table": "^0.1.0",
|
|
67
|
+
"@matthesketh/ink-tabs": "^0.1.0",
|
|
68
|
+
"@matthesketh/ink-task-list": "0.1.0",
|
|
69
|
+
"@matthesketh/ink-timeline": "0.1.0",
|
|
70
|
+
"@matthesketh/ink-toast": "^0.1.0",
|
|
71
|
+
"@matthesketh/ink-viewport": "^0.1.0",
|
|
72
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
73
|
+
"better-sqlite3": "12.9.0",
|
|
74
|
+
"chokidar": "5.0.0",
|
|
67
75
|
"ink": "^5.2.1",
|
|
68
76
|
"ink-spinner": "^5.0.0",
|
|
69
77
|
"ink-text-input": "^6.0.0",
|
|
78
|
+
"proper-lockfile": "4.1.2",
|
|
70
79
|
"react": "^18.3.1",
|
|
71
80
|
"zod": "^3.24.0"
|
|
72
81
|
},
|
|
73
82
|
"devDependencies": {
|
|
83
|
+
"@types/better-sqlite3": "7.6.13",
|
|
74
84
|
"@types/node": "20.17.0",
|
|
85
|
+
"@types/proper-lockfile": "4.1.4",
|
|
75
86
|
"@types/react": "^18.3.28",
|
|
76
|
-
"
|
|
87
|
+
"ink-testing-library": "^4.0.0",
|
|
88
|
+
"tsc-alias": "1.8.16",
|
|
89
|
+
"tsx": "4.21.0",
|
|
77
90
|
"typescript": "5.6.3",
|
|
78
91
|
"vitest": "4.0.18"
|
|
79
92
|
}
|
|
80
|
-
}
|
|
93
|
+
}
|