@a9s/cli 0.0.1 → 1.0.6

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 (95) hide show
  1. package/README.md +167 -2
  2. package/dist/scripts/seed.js +310 -0
  3. package/dist/src/App.js +476 -0
  4. package/dist/src/adapters/ServiceAdapter.js +1 -0
  5. package/dist/src/adapters/capabilities/ActionCapability.js +1 -0
  6. package/dist/src/adapters/capabilities/DetailCapability.js +1 -0
  7. package/dist/src/adapters/capabilities/EditCapability.js +1 -0
  8. package/dist/src/adapters/capabilities/YankCapability.js +42 -0
  9. package/dist/src/adapters/capabilities/YankCapability.test.js +29 -0
  10. package/dist/src/components/AdvancedTextInput.js +200 -0
  11. package/dist/src/components/AdvancedTextInput.test.js +190 -0
  12. package/dist/src/components/AutocompleteInput.js +29 -0
  13. package/dist/src/components/DetailPanel.js +12 -0
  14. package/dist/src/components/DiffViewer.js +17 -0
  15. package/dist/src/components/ErrorStatePanel.js +5 -0
  16. package/dist/src/components/HUD.js +31 -0
  17. package/dist/src/components/HelpPanel.js +33 -0
  18. package/dist/src/components/ModeBar.js +43 -0
  19. package/dist/src/components/Table/index.js +109 -0
  20. package/dist/src/components/Table/widths.js +19 -0
  21. package/dist/src/components/TableSkeleton.js +25 -0
  22. package/dist/src/components/YankHelpPanel.js +43 -0
  23. package/dist/src/constants/commands.js +15 -0
  24. package/dist/src/constants/keybindings.js +530 -0
  25. package/dist/src/constants/keys.js +37 -0
  26. package/dist/src/features/AppMainView.integration.test.js +133 -0
  27. package/dist/src/features/AppMainView.js +95 -0
  28. package/dist/src/hooks/inputEvents.js +1 -0
  29. package/dist/src/hooks/mainInputScopes.js +68 -0
  30. package/dist/src/hooks/mainInputScopes.test.js +24 -0
  31. package/dist/src/hooks/useActionController.js +78 -0
  32. package/dist/src/hooks/useAppController.js +102 -0
  33. package/dist/src/hooks/useAppController.test.js +54 -0
  34. package/dist/src/hooks/useAppData.js +48 -0
  35. package/dist/src/hooks/useAwsContext.js +77 -0
  36. package/dist/src/hooks/useAwsProfiles.js +53 -0
  37. package/dist/src/hooks/useAwsRegions.js +105 -0
  38. package/dist/src/hooks/useCommandRouter.js +56 -0
  39. package/dist/src/hooks/useCommandRouter.test.js +27 -0
  40. package/dist/src/hooks/useDetailController.js +57 -0
  41. package/dist/src/hooks/useDetailController.test.js +32 -0
  42. package/dist/src/hooks/useHelpPanel.js +65 -0
  43. package/dist/src/hooks/useHierarchyState.js +39 -0
  44. package/dist/src/hooks/useInputEventProcessor.js +450 -0
  45. package/dist/src/hooks/useInputEventProcessor.test.js +174 -0
  46. package/dist/src/hooks/useKeyChord.js +83 -0
  47. package/dist/src/hooks/useMainInput.js +18 -0
  48. package/dist/src/hooks/useNavigation.js +47 -0
  49. package/dist/src/hooks/usePendingAction.js +8 -0
  50. package/dist/src/hooks/usePickerManager.js +130 -0
  51. package/dist/src/hooks/usePickerState.js +47 -0
  52. package/dist/src/hooks/usePickerTable.js +20 -0
  53. package/dist/src/hooks/useServiceView.js +226 -0
  54. package/dist/src/hooks/useUiHints.js +60 -0
  55. package/dist/src/hooks/useYankMode.js +24 -0
  56. package/dist/src/hooks/yankHeaderMarkers.js +23 -0
  57. package/dist/src/hooks/yankHeaderMarkers.test.js +49 -0
  58. package/dist/src/index.js +30 -0
  59. package/dist/src/services.js +12 -0
  60. package/dist/src/state/atoms.js +27 -0
  61. package/dist/src/types.js +12 -0
  62. package/dist/src/utils/aws.js +39 -0
  63. package/dist/src/utils/debugLogger.js +34 -0
  64. package/dist/src/utils/secretDisplay.js +45 -0
  65. package/dist/src/utils/withFullscreen.js +38 -0
  66. package/dist/src/views/dynamodb/adapter.js +22 -0
  67. package/dist/src/views/iam/adapter.js +258 -0
  68. package/dist/src/views/iam/capabilities/detailCapability.js +93 -0
  69. package/dist/src/views/iam/capabilities/editCapability.js +59 -0
  70. package/dist/src/views/iam/capabilities/yankCapability.js +6 -0
  71. package/dist/src/views/iam/capabilities/yankOptions.js +15 -0
  72. package/dist/src/views/iam/schema.js +7 -0
  73. package/dist/src/views/iam/types.js +1 -0
  74. package/dist/src/views/iam/utils.js +21 -0
  75. package/dist/src/views/route53/adapter.js +22 -0
  76. package/dist/src/views/s3/adapter.js +154 -0
  77. package/dist/src/views/s3/capabilities/actionCapability.js +172 -0
  78. package/dist/src/views/s3/capabilities/detailCapability.js +115 -0
  79. package/dist/src/views/s3/capabilities/editCapability.js +35 -0
  80. package/dist/src/views/s3/capabilities/yankCapability.js +6 -0
  81. package/dist/src/views/s3/capabilities/yankOptions.js +55 -0
  82. package/dist/src/views/s3/client.js +12 -0
  83. package/dist/src/views/s3/fetcher.js +86 -0
  84. package/dist/src/views/s3/schema.js +6 -0
  85. package/dist/src/views/s3/utils.js +19 -0
  86. package/dist/src/views/secretsmanager/adapter.js +188 -0
  87. package/dist/src/views/secretsmanager/capabilities/actionCapability.js +193 -0
  88. package/dist/src/views/secretsmanager/capabilities/detailCapability.js +46 -0
  89. package/dist/src/views/secretsmanager/capabilities/editCapability.js +116 -0
  90. package/dist/src/views/secretsmanager/capabilities/yankCapability.js +7 -0
  91. package/dist/src/views/secretsmanager/capabilities/yankOptions.js +68 -0
  92. package/dist/src/views/secretsmanager/schema.js +28 -0
  93. package/dist/src/views/secretsmanager/types.js +1 -0
  94. package/package.json +72 -5
  95. package/index.js +0 -1
@@ -0,0 +1,109 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React, { useMemo } from "react";
3
+ import { Box, Text } from "ink";
4
+ import { computeColumnWidths } from "./widths.js";
5
+ // Color constants for table styling
6
+ const COLORS = {
7
+ separator: "gray", // │ and ─ dividers
8
+ headerText: "blue", // Column header text
9
+ selectedBg: "cyan", // Selected row background
10
+ selectedText: "black", // Selected row text
11
+ highlightText: "yellow", // Filtered match highlight
12
+ emptyText: "gray", // Empty state text
13
+ highlightSelectedText: "black", // Highlight text on selected row
14
+ };
15
+ function truncate(str, maxLen) {
16
+ if (str.length <= maxLen)
17
+ return str.padEnd(maxLen);
18
+ return str.slice(0, maxLen - 1) + "…";
19
+ }
20
+ function truncateNoPad(str, maxLen) {
21
+ if (maxLen <= 0)
22
+ return "";
23
+ if (str.length <= maxLen)
24
+ return str;
25
+ if (maxLen === 1)
26
+ return "…";
27
+ return str.slice(0, maxLen - 1) + "…";
28
+ }
29
+ function highlightMatch(text, filter, isSelected = false) {
30
+ if (!filter || !text)
31
+ return [text];
32
+ const parts = [];
33
+ const lowerText = text.toLowerCase();
34
+ const lowerFilter = filter.toLowerCase();
35
+ let lastIdx = 0;
36
+ const highlightColor = isSelected ? COLORS.highlightSelectedText : COLORS.highlightText;
37
+ let idx = lowerText.indexOf(lowerFilter);
38
+ while (idx !== -1) {
39
+ if (idx > lastIdx) {
40
+ parts.push(text.slice(lastIdx, idx));
41
+ }
42
+ parts.push(_jsx(Text, { color: highlightColor, bold: true, children: text.slice(idx, idx + filter.length) }, `match-${idx}`));
43
+ lastIdx = idx + filter.length;
44
+ idx = lowerText.indexOf(lowerFilter, lastIdx);
45
+ }
46
+ if (lastIdx < text.length) {
47
+ parts.push(text.slice(lastIdx));
48
+ }
49
+ return parts.length > 0 ? parts : [text];
50
+ }
51
+ const Row = React.memo(function Row({ row, isSelected, columns, colWidths, filterText }) {
52
+ const parts = [];
53
+ columns.forEach((col, i) => {
54
+ if (i > 0)
55
+ parts.push(_jsxs(Text, { color: COLORS.separator, children: [" ", "\u2502", " "] }, `sep-${i}`));
56
+ const cellData = row.cells[col.key] ?? "";
57
+ const cellValue = typeof cellData === "string" ? cellData : cellData.displayName;
58
+ const truncated = truncate(cellValue, colWidths[i]);
59
+ const highlighted = filterText && truncated ? highlightMatch(truncated, filterText, isSelected) : [truncated];
60
+ if (isSelected) {
61
+ parts.push(_jsx(Text, { color: COLORS.selectedText, bold: true, children: highlighted }, `cell-${i}`));
62
+ }
63
+ else {
64
+ parts.push(_jsx(Text, { children: highlighted }, `cell-${i}`));
65
+ }
66
+ });
67
+ return isSelected ? _jsx(Box, { backgroundColor: COLORS.selectedBg, children: parts }) : _jsx(Box, { children: parts });
68
+ });
69
+ export const Table = React.memo(function Table({ columns, rows, selectedIndex, filterText, terminalWidth, maxHeight, scrollOffset, contextLabel, headerMarkers, }) {
70
+ // Memoize column widths computation
71
+ const colWidths = useMemo(() => computeColumnWidths(columns, terminalWidth), [columns, terminalWidth]);
72
+ // Rows are pre-filtered by parent, no need to filter again
73
+ const visibleRows = rows.slice(scrollOffset, scrollOffset + maxHeight);
74
+ const adjustedSelected = selectedIndex - scrollOffset;
75
+ const renderHeader = () => {
76
+ const parts = [];
77
+ columns.forEach((col, i) => {
78
+ if (i > 0)
79
+ parts.push(_jsxs(Text, { color: COLORS.separator, children: [" ", "\u2502", " "] }, `sep-${i}`));
80
+ const width = colWidths[i];
81
+ const markers = headerMarkers?.[col.key] ?? [];
82
+ const markerText = markers.length > 0 ? ` [${markers.join(",")}]` : "";
83
+ if (!markerText) {
84
+ parts.push(_jsx(Text, { bold: true, color: COLORS.headerText, children: truncate(col.label, width) }, col.key));
85
+ return;
86
+ }
87
+ if (markerText.length >= width) {
88
+ const markerDisplay = truncate(markerText, width);
89
+ parts.push(_jsx(Text, { color: "cyan", children: markerDisplay }, `${col.key}-markers-only`));
90
+ return;
91
+ }
92
+ const labelMax = width - markerText.length;
93
+ const labelDisplay = truncateNoPad(col.label, labelMax);
94
+ const trailingPadLen = Math.max(0, width - (labelDisplay.length + markerText.length));
95
+ parts.push(_jsx(Text, { bold: true, color: COLORS.headerText, children: labelDisplay }, `${col.key}-label`));
96
+ parts.push(_jsx(Text, { color: "cyan", children: markerText }, `${col.key}-markers`));
97
+ if (trailingPadLen > 0) {
98
+ parts.push(_jsx(Text, { color: COLORS.headerText, children: " ".repeat(trailingPadLen) }, `${col.key}-pad`));
99
+ }
100
+ });
101
+ return _jsx(Box, { children: parts });
102
+ };
103
+ const renderDivider = () => (_jsx(Text, { color: COLORS.separator, children: columns.map((col, i) => "─".repeat(colWidths[i])).join("─┼─") }));
104
+ const renderEmpty = () => (_jsx(Text, { color: COLORS.emptyText, children: filterText ? `No results for "${filterText}"` : "No items" }));
105
+ if (rows.length === 0) {
106
+ return (_jsxs(Box, { flexDirection: "column", children: [contextLabel && (_jsx(Text, { bold: true, color: COLORS.headerText, children: contextLabel })), contextLabel && _jsx(Box, { height: 1 }), renderHeader(), renderDivider(), renderEmpty()] }));
107
+ }
108
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [contextLabel && (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, color: COLORS.headerText, children: contextLabel }), _jsx(Box, { height: 1 })] })), renderHeader(), renderDivider(), _jsx(Box, { flexDirection: "column", flexGrow: 1, children: visibleRows.map((row, i) => (_jsx(Row, { row: row, isSelected: i === adjustedSelected, columns: columns, colWidths: colWidths, filterText: filterText }, row.id))) }), rows.length > maxHeight && (_jsx(Box, { paddingTop: 1, children: _jsxs(Text, { color: "gray", children: [scrollOffset + visibleRows.length, " / ", rows.length, " items"] }) }))] }));
109
+ });
@@ -0,0 +1,19 @@
1
+ export function computeColumnWidths(columns, terminalWidth) {
2
+ const MIN_WIDTH = 4;
3
+ const GAP = 3; // " │ " between columns (space-bar-space = 3 chars)
4
+ const totalGaps = (columns.length - 1) * GAP;
5
+ const available = Math.max(0, terminalWidth - totalGaps);
6
+ // Assign fixed widths first
7
+ let fixedTotal = 0;
8
+ const widths = columns.map((col) => {
9
+ if (col.width !== undefined) {
10
+ fixedTotal += col.width;
11
+ return col.width;
12
+ }
13
+ return null;
14
+ });
15
+ const flexColumns = widths.filter((w) => w === null).length;
16
+ const flexAvailable = Math.max(available - fixedTotal, flexColumns * MIN_WIDTH);
17
+ const flexWidth = flexColumns > 0 ? Math.floor(flexAvailable / flexColumns) : 0;
18
+ return widths.map((w) => (w !== null ? w : Math.max(flexWidth, MIN_WIDTH)));
19
+ }
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from "react";
3
+ import { Box, Text } from "ink";
4
+ import { computeColumnWidths } from "./Table/widths.js";
5
+ function fill(len, ch = "░") {
6
+ return ch.repeat(Math.max(1, len));
7
+ }
8
+ function truncate(str, maxLen) {
9
+ if (str.length <= maxLen)
10
+ return str.padEnd(maxLen);
11
+ return str.slice(0, Math.max(1, maxLen - 1)) + "…";
12
+ }
13
+ export function TableSkeleton({ columns, terminalWidth, rows = 8, contextLabel, }) {
14
+ const FRAMES = ["░", "▒", "▓"];
15
+ const [frame, setFrame] = React.useState(0);
16
+ const colWidths = computeColumnWidths(columns, terminalWidth);
17
+ React.useEffect(() => {
18
+ const timer = setInterval(() => {
19
+ setFrame((prev) => (prev + 1) % FRAMES.length);
20
+ }, 180);
21
+ return () => clearInterval(timer);
22
+ }, []);
23
+ const shade = FRAMES[frame] ?? "░";
24
+ return (_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [contextLabel ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, color: "blue", children: contextLabel }), _jsx(Box, { height: 1 })] })) : null, _jsx(Box, { children: columns.map((col, i) => (_jsxs(React.Fragment, { children: [i > 0 ? _jsx(Text, { color: "gray", children: " \u2502 " }) : null, _jsx(Text, { bold: true, color: "blue", children: truncate(col.label, colWidths[i] ?? 1) })] }, col.key))) }), _jsx(Text, { color: "gray", children: columns.map((_, i) => fill(colWidths[i] ?? 1, "─")).join("─┼─") }), Array.from({ length: rows }).map((_, rowIdx) => (_jsx(Box, { children: columns.map((col, i) => (_jsxs(React.Fragment, { children: [i > 0 ? _jsx(Text, { color: "gray", children: " \u2502 " }) : null, _jsx(Text, { color: "gray", children: fill(Math.max(1, colWidths[i] ?? 1), shade) })] }, `${col.key}-${rowIdx}`))) }, `skeleton-row-${rowIdx}`)))] }));
25
+ }
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useState } from "react";
3
+ import { Box, Text } from "ink";
4
+ import { Alert, Badge, StatusMessage, UnorderedList } from "@inkjs/ui";
5
+ import { triggerToString } from "../constants/keybindings.js";
6
+ export function YankHelpPanel({ options, row }) {
7
+ const [resolvedValues, setResolvedValues] = useState({});
8
+ useEffect(() => {
9
+ let isActive = true;
10
+ if (!row) {
11
+ setResolvedValues({});
12
+ return () => {
13
+ isActive = false;
14
+ };
15
+ }
16
+ const load = async () => {
17
+ const entries = await Promise.all(options.map(async (option) => {
18
+ const id = `${option.label}-${triggerToString(option.trigger)}`;
19
+ try {
20
+ const value = await option.resolve(row);
21
+ return [id, value ?? "(empty)"];
22
+ }
23
+ catch (error) {
24
+ return [id, `(error: ${error.message})`];
25
+ }
26
+ }));
27
+ if (!isActive)
28
+ return;
29
+ const next = {};
30
+ for (const [id, value] of entries) {
31
+ next[id] = value;
32
+ }
33
+ setResolvedValues(next);
34
+ };
35
+ void load();
36
+ return () => {
37
+ isActive = false;
38
+ };
39
+ }, [options, row]);
40
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Alert, { variant: "info", title: "Yank Options", children: "Press key to copy, Esc or ? to close" }), _jsx(Box, { height: 1 }), !row && _jsx(StatusMessage, { variant: "warning", children: "No row selected" }), _jsx(UnorderedList, { children: options.map((option) => (_jsx(UnorderedList.Item, { children: _jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Badge, { color: "yellow", children: triggerToString(option.trigger) }), _jsxs(Text, { children: [" ", option.label] })] }), _jsx(Text, { color: "gray", children: row
41
+ ? ` -> ${resolvedValues[`${option.label}-${triggerToString(option.trigger)}`] ?? "(loading...)"}`
42
+ : " -> (no value)" })] }) }, `${option.label}-${triggerToString(option.trigger)}`))) })] }));
43
+ }
@@ -0,0 +1,15 @@
1
+ import { SERVICE_REGISTRY } from "../services.js";
2
+ const SERVICE_COMMANDS = Object.keys(SERVICE_REGISTRY);
3
+ export const AVAILABLE_COMMANDS = [
4
+ ...SERVICE_COMMANDS,
5
+ "regions",
6
+ "profiles",
7
+ "resources",
8
+ "region",
9
+ "profile",
10
+ "use-region",
11
+ "use-profile",
12
+ "$default",
13
+ "quit",
14
+ ];
15
+ export const COMMAND_MODE_HINT = " Commands: s3 route53 dynamodb iam quit regions profiles resources • Enter run • Esc cancel";