@matthesketh/fleet 1.0.0 → 1.2.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.
Files changed (67) hide show
  1. package/README.md +27 -4
  2. package/dist/cli.js +8 -0
  3. package/dist/commands/deps.d.ts +1 -0
  4. package/dist/commands/deps.js +223 -0
  5. package/dist/commands/motd.d.ts +1 -0
  6. package/dist/commands/motd.js +10 -0
  7. package/dist/core/deps/actors/pr-creator.d.ts +14 -0
  8. package/dist/core/deps/actors/pr-creator.js +103 -0
  9. package/dist/core/deps/cache.d.ts +5 -0
  10. package/dist/core/deps/cache.js +28 -0
  11. package/dist/core/deps/collectors/composer.d.ts +12 -0
  12. package/dist/core/deps/collectors/composer.js +70 -0
  13. package/dist/core/deps/collectors/docker-image.d.ts +18 -0
  14. package/dist/core/deps/collectors/docker-image.js +132 -0
  15. package/dist/core/deps/collectors/docker-running.d.ts +17 -0
  16. package/dist/core/deps/collectors/docker-running.js +55 -0
  17. package/dist/core/deps/collectors/eol.d.ts +16 -0
  18. package/dist/core/deps/collectors/eol.js +139 -0
  19. package/dist/core/deps/collectors/github-pr.d.ts +8 -0
  20. package/dist/core/deps/collectors/github-pr.js +40 -0
  21. package/dist/core/deps/collectors/npm.d.ts +12 -0
  22. package/dist/core/deps/collectors/npm.js +63 -0
  23. package/dist/core/deps/collectors/pip.d.ts +15 -0
  24. package/dist/core/deps/collectors/pip.js +94 -0
  25. package/dist/core/deps/collectors/vulnerability.d.ts +9 -0
  26. package/dist/core/deps/collectors/vulnerability.js +102 -0
  27. package/dist/core/deps/config.d.ts +6 -0
  28. package/dist/core/deps/config.js +55 -0
  29. package/dist/core/deps/reporters/cli.d.ts +4 -0
  30. package/dist/core/deps/reporters/cli.js +123 -0
  31. package/dist/core/deps/reporters/motd.d.ts +3 -0
  32. package/dist/core/deps/reporters/motd.js +64 -0
  33. package/dist/core/deps/reporters/telegram.d.ts +6 -0
  34. package/dist/core/deps/reporters/telegram.js +106 -0
  35. package/dist/core/deps/scanner.d.ts +4 -0
  36. package/dist/core/deps/scanner.js +89 -0
  37. package/dist/core/deps/severity.d.ts +6 -0
  38. package/dist/core/deps/severity.js +45 -0
  39. package/dist/core/deps/types.d.ts +64 -0
  40. package/dist/core/deps/types.js +1 -0
  41. package/dist/mcp/deps-tools.d.ts +2 -0
  42. package/dist/mcp/deps-tools.js +81 -0
  43. package/dist/mcp/server.js +2 -0
  44. package/dist/templates/motd.d.ts +1 -0
  45. package/dist/templates/motd.js +7 -0
  46. package/dist/tui/components/AppList.js +1 -1
  47. package/dist/tui/components/Confirm.js +3 -4
  48. package/dist/tui/components/Header.js +37 -8
  49. package/dist/tui/components/KeyHint.js +4 -5
  50. package/dist/tui/hooks/use-terminal-size.d.ts +1 -0
  51. package/dist/tui/hooks/use-terminal-size.js +1 -0
  52. package/dist/tui/router.js +81 -9
  53. package/dist/tui/state.js +15 -0
  54. package/dist/tui/tests/flicker.test.d.ts +1 -0
  55. package/dist/tui/tests/flicker.test.js +105 -0
  56. package/dist/tui/tests/keyboard-integration.test.d.ts +1 -0
  57. package/dist/tui/tests/keyboard-integration.test.js +117 -0
  58. package/dist/tui/tests/test-app.d.ts +4 -0
  59. package/dist/tui/tests/test-app.js +79 -0
  60. package/dist/tui/types.d.ts +13 -0
  61. package/dist/tui/views/AppDetail.js +41 -26
  62. package/dist/tui/views/Dashboard.js +34 -9
  63. package/dist/tui/views/HealthView.js +36 -12
  64. package/dist/tui/views/LogsView.js +14 -9
  65. package/dist/tui/views/SecretEdit.js +8 -4
  66. package/dist/tui/views/SecretsView.js +49 -36
  67. package/package.json +17 -1
@@ -1,72 +1,84 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useState } from 'react';
3
- import { Box, Text, useInput } from 'ink';
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 { selectedApp } = useAppState();
11
+ const state = useAppState();
9
12
  const dispatch = useAppDispatch();
10
13
  const redact = useRedact();
11
14
  const secrets = useSecrets();
12
- const [subView, setSubView] = useState('app-list');
13
- const [selectedIndex, setSelectedIndex] = useState(0);
15
+ const availableHeight = useAvailableHeight();
16
+ const { secretsSubView: subView, secretsIndex: selectedIndex, selectedApp } = state;
17
+ const refresh = secrets.refresh;
14
18
  useEffect(() => {
15
- secrets.refresh();
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
- useInput((input, key) => {
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
- setSelectedIndex(prev => Math.min(prev + 1, secrets.apps.length - 1));
29
+ dispatch({ type: 'SET_INDEX', view: 'secrets', index: Math.min(selectedIndex + 1, secrets.apps.length - 1) });
30
+ return true;
26
31
  }
27
- else if (input === 'k' || key.upArrow) {
28
- setSelectedIndex(prev => Math.max(prev - 1, 0));
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
- else if (key.return && secrets.apps[selectedIndex]) {
36
+ if (key.return && secrets.apps[selectedIndex]) {
31
37
  dispatch({ type: 'SELECT_APP', app: secrets.apps[selectedIndex].app });
32
- setSubView('secret-list');
33
- setSelectedIndex(0);
38
+ dispatch({ type: 'SET_SECRETS_SUBVIEW', subView: 'secret-list' });
39
+ return true;
34
40
  }
35
- else if (input === 'u') {
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
- else if (input === 'l') {
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') {
51
59
  if (input === 'j' || key.downArrow) {
52
- setSelectedIndex(prev => Math.min(prev + 1, secrets.secrets.length - 1));
60
+ dispatch({ type: 'SET_INDEX', view: 'secrets', index: Math.min(selectedIndex + 1, secrets.secrets.length - 1) });
61
+ return true;
53
62
  }
54
- else if (input === 'k' || key.upArrow) {
55
- setSelectedIndex(prev => Math.max(prev - 1, 0));
63
+ if (input === 'k' || key.upArrow) {
64
+ dispatch({ type: 'SET_INDEX', view: 'secrets', index: Math.max(selectedIndex - 1, 0) });
65
+ return true;
56
66
  }
57
- else if (key.return && secrets.secrets[selectedIndex] && selectedApp) {
67
+ if (key.return && secrets.secrets[selectedIndex] && selectedApp) {
58
68
  dispatch({ type: 'SELECT_SECRET', key: secrets.secrets[selectedIndex].key });
59
69
  dispatch({ type: 'NAVIGATE', view: 'secret-edit' });
70
+ return true;
60
71
  }
61
- else if (key.escape) {
62
- setSubView('app-list');
63
- setSelectedIndex(0);
72
+ if (key.escape) {
73
+ dispatch({ type: 'SET_SECRETS_SUBVIEW', subView: 'app-list' });
74
+ return true;
64
75
  }
65
- else if (input === 'a' && selectedApp) {
76
+ if (input === 'a' && selectedApp) {
66
77
  dispatch({ type: 'SELECT_SECRET', key: null });
67
78
  dispatch({ type: 'NAVIGATE', view: 'secret-edit' });
79
+ return true;
68
80
  }
69
- else if (input === 'd' && selectedApp && secrets.secrets[selectedIndex]) {
81
+ if (input === 'd' && selectedApp && secrets.secrets[selectedIndex]) {
70
82
  const secretKey = secrets.secrets[selectedIndex].key;
71
83
  dispatch({
72
84
  type: 'CONFIRM',
@@ -85,8 +97,9 @@ export function SecretsView() {
85
97
  },
86
98
  },
87
99
  });
100
+ return true;
88
101
  }
89
- else if (input === 'r' && selectedApp && secrets.secrets[selectedIndex]) {
102
+ if (input === 'r' && selectedApp && secrets.secrets[selectedIndex]) {
90
103
  const secretKey = secrets.secrets[selectedIndex].key;
91
104
  if (secrets.revealedValues[secretKey]) {
92
105
  secrets.hideSecret(secretKey);
@@ -94,15 +107,15 @@ export function SecretsView() {
94
107
  else {
95
108
  secrets.revealSecret(selectedApp, secretKey);
96
109
  }
110
+ return true;
97
111
  }
98
112
  }
99
- });
100
- 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:" }), secrets.apps.length === 0 ? (_jsx(Text, { color: colors.muted, children: " No secrets managed" })) : (secrets.apps.map((app, i) => {
101
- const selected = i === selectedIndex;
102
- return (_jsxs(Text, { 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"] })] }, app.app));
103
- }))] })) : (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, color: colors.primary, children: redact(selectedApp ?? '') }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: secrets.secrets.length === 0 ? (_jsx(Text, { color: colors.muted, children: " No secrets found" })) : (secrets.secrets.map((secret, i) => {
104
- const selected = i === selectedIndex;
105
- const revealed = secrets.revealedValues[secret.key];
106
- return (_jsxs(Text, { 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 })] }, secret.key));
107
- })) })] }))] }));
113
+ return false;
114
+ }, [subView, selectedIndex, selectedApp, secrets, dispatch, redact]);
115
+ useRegisterHandler(handler);
116
+ const listHeight = Math.max(5, availableHeight - 5);
117
+ 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) => {
118
+ const revealed = secrets.revealedValues[secret.key];
119
+ 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 })] }));
120
+ } }) })] }))] }));
108
121
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@matthesketh/fleet",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Docker production management CLI + MCP server for Claude Code",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,6 +13,7 @@
13
13
  "LICENSE",
14
14
  "README.md"
15
15
  ],
16
+ "workspaces": ["packages/*"],
16
17
  "scripts": {
17
18
  "build": "tsc",
18
19
  "dev": "tsx src/index.ts",
@@ -48,6 +49,21 @@
48
49
  },
49
50
  "dependencies": {
50
51
  "@modelcontextprotocol/sdk": "1.8.0",
52
+ "@matthesketh/ink-breadcrumb": "*",
53
+ "@matthesketh/ink-gauge": "*",
54
+ "@matthesketh/ink-input-dispatcher": "*",
55
+ "@matthesketh/ink-keybinding-help": "*",
56
+ "@matthesketh/ink-log-viewer": "*",
57
+ "@matthesketh/ink-modal": "*",
58
+ "@matthesketh/ink-pipeline": "*",
59
+ "@matthesketh/ink-rule": "*",
60
+ "@matthesketh/ink-scrollable-list": "*",
61
+ "@matthesketh/ink-split-pane": "*",
62
+ "@matthesketh/ink-status-bar": "*",
63
+ "@matthesketh/ink-table": "*",
64
+ "@matthesketh/ink-tabs": "*",
65
+ "@matthesketh/ink-toast": "*",
66
+ "@matthesketh/ink-viewport": "*",
51
67
  "ink": "^5.2.1",
52
68
  "ink-spinner": "^5.0.0",
53
69
  "ink-text-input": "^6.0.0",