@a9s/cli 1.0.8 → 1.0.9
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/dist/src/App.js +35 -3
- package/dist/src/components/AdvancedTextInput.js +3 -1
- package/dist/src/components/AutocompleteInput.js +3 -1
- package/dist/src/components/DetailPanel.js +3 -1
- package/dist/src/components/DiffViewer.js +3 -1
- package/dist/src/components/ErrorStatePanel.js +3 -1
- package/dist/src/components/HUD.js +3 -1
- package/dist/src/components/HelpPanel.js +6 -4
- package/dist/src/components/ModeBar.js +5 -8
- package/dist/src/components/Table/index.js +19 -26
- package/dist/src/components/TableSkeleton.js +3 -1
- package/dist/src/components/YankHelpPanel.js +3 -1
- package/dist/src/constants/commands.js +2 -1
- package/dist/src/constants/theme.js +608 -0
- package/dist/src/contexts/ThemeContext.js +13 -0
- package/dist/src/features/AppMainView.integration.test.js +1 -0
- package/dist/src/features/AppMainView.js +6 -4
- package/dist/src/hooks/useCommandRouter.js +5 -0
- package/dist/src/hooks/usePickerManager.js +35 -1
- package/dist/src/index.js +2 -1
- package/dist/src/state/atoms.js +3 -0
- package/dist/src/utils/config.js +36 -0
- package/dist/src/views/dynamodb/adapter.js +2 -1
- package/dist/src/views/iam/adapter.js +2 -1
- package/dist/src/views/route53/adapter.js +2 -1
- package/dist/src/views/s3/adapter.js +2 -1
- package/dist/src/views/secretsmanager/adapter.js +2 -1
- package/package.json +2 -1
|
@@ -8,6 +8,8 @@ export function parseCommand(input) {
|
|
|
8
8
|
return { type: "openRegions" };
|
|
9
9
|
if (command === "resources")
|
|
10
10
|
return { type: "openResources" };
|
|
11
|
+
if (command === "theme")
|
|
12
|
+
return { type: "openThemePicker" };
|
|
11
13
|
const regionMatch = command.match(/^(region|use-region)\s+([a-z0-9-]+)$/i);
|
|
12
14
|
if (regionMatch?.[2]) {
|
|
13
15
|
return { type: "setRegion", region: regionMatch[2].toLowerCase() };
|
|
@@ -37,6 +39,9 @@ export function useCommandRouter(args) {
|
|
|
37
39
|
case "openResources":
|
|
38
40
|
args.openResourcePicker();
|
|
39
41
|
return;
|
|
42
|
+
case "openThemePicker":
|
|
43
|
+
args.openThemePicker();
|
|
44
|
+
return;
|
|
40
45
|
case "setRegion":
|
|
41
46
|
args.setSelectedRegion(parsed.region);
|
|
42
47
|
return;
|
|
@@ -3,10 +3,12 @@ import { textCell } from "../types.js";
|
|
|
3
3
|
import { usePickerState } from "./usePickerState.js";
|
|
4
4
|
import { usePickerTable } from "./usePickerTable.js";
|
|
5
5
|
import { SERVICE_REGISTRY } from "../services.js";
|
|
6
|
+
import { THEMES, THEME_LABELS } from "../constants/theme.js";
|
|
6
7
|
export function usePickerManager({ tableHeight, availableRegions, availableProfiles, }) {
|
|
7
8
|
const region = usePickerState();
|
|
8
9
|
const profile = usePickerState();
|
|
9
10
|
const resource = usePickerState();
|
|
11
|
+
const theme = usePickerState();
|
|
10
12
|
const regionRows = useMemo(() => availableRegions.map((r) => ({
|
|
11
13
|
id: r.name,
|
|
12
14
|
cells: { region: textCell(r.name), description: textCell(r.description) },
|
|
@@ -25,6 +27,14 @@ export function usePickerManager({ tableHeight, availableRegions, availableProfi
|
|
|
25
27
|
},
|
|
26
28
|
meta: {},
|
|
27
29
|
})), []);
|
|
30
|
+
const themeRows = useMemo(() => Object.keys(THEMES).map((themeName) => ({
|
|
31
|
+
id: themeName,
|
|
32
|
+
cells: {
|
|
33
|
+
theme: textCell(THEME_LABELS[themeName]),
|
|
34
|
+
id: textCell(themeName),
|
|
35
|
+
},
|
|
36
|
+
meta: {},
|
|
37
|
+
})), []);
|
|
28
38
|
const regionTable = usePickerTable({
|
|
29
39
|
rows: regionRows,
|
|
30
40
|
filterText: region.filter,
|
|
@@ -40,6 +50,11 @@ export function usePickerManager({ tableHeight, availableRegions, availableProfi
|
|
|
40
50
|
filterText: resource.filter,
|
|
41
51
|
maxHeight: tableHeight,
|
|
42
52
|
});
|
|
53
|
+
const themeTable = usePickerTable({
|
|
54
|
+
rows: themeRows,
|
|
55
|
+
filterText: theme.filter,
|
|
56
|
+
maxHeight: tableHeight,
|
|
57
|
+
});
|
|
43
58
|
const regionColumns = [
|
|
44
59
|
{ key: "region", label: "Region" },
|
|
45
60
|
{ key: "description", label: "Description" },
|
|
@@ -52,6 +67,10 @@ export function usePickerManager({ tableHeight, availableRegions, availableProfi
|
|
|
52
67
|
{ key: "resource", label: "Resource" },
|
|
53
68
|
{ key: "description", label: "Description" },
|
|
54
69
|
];
|
|
70
|
+
const themeColumns = [
|
|
71
|
+
{ key: "theme", label: "Theme" },
|
|
72
|
+
{ key: "id", label: "ID" },
|
|
73
|
+
];
|
|
55
74
|
const regionEntry = {
|
|
56
75
|
id: "region",
|
|
57
76
|
columns: regionColumns,
|
|
@@ -73,13 +92,22 @@ export function usePickerManager({ tableHeight, availableRegions, availableProfi
|
|
|
73
92
|
...resource,
|
|
74
93
|
...resourceTable,
|
|
75
94
|
};
|
|
95
|
+
const themeEntry = {
|
|
96
|
+
id: "theme",
|
|
97
|
+
columns: themeColumns,
|
|
98
|
+
contextLabel: "Select Theme",
|
|
99
|
+
...theme,
|
|
100
|
+
...themeTable,
|
|
101
|
+
};
|
|
76
102
|
const activePicker = regionEntry.open
|
|
77
103
|
? regionEntry
|
|
78
104
|
: profileEntry.open
|
|
79
105
|
? profileEntry
|
|
80
106
|
: resourceEntry.open
|
|
81
107
|
? resourceEntry
|
|
82
|
-
:
|
|
108
|
+
: themeEntry.open
|
|
109
|
+
? themeEntry
|
|
110
|
+
: null;
|
|
83
111
|
const getEntry = (id) => {
|
|
84
112
|
switch (id) {
|
|
85
113
|
case "region":
|
|
@@ -88,6 +116,8 @@ export function usePickerManager({ tableHeight, availableRegions, availableProfi
|
|
|
88
116
|
return profileEntry;
|
|
89
117
|
case "resource":
|
|
90
118
|
return resourceEntry;
|
|
119
|
+
case "theme":
|
|
120
|
+
return themeEntry;
|
|
91
121
|
}
|
|
92
122
|
};
|
|
93
123
|
const openPicker = (id) => {
|
|
@@ -114,6 +144,9 @@ export function usePickerManager({ tableHeight, availableRegions, availableProfi
|
|
|
114
144
|
case "profile":
|
|
115
145
|
handlers.onSelectProfile(activePicker.selectedRow.id);
|
|
116
146
|
break;
|
|
147
|
+
case "theme":
|
|
148
|
+
handlers.onSelectTheme(activePicker.selectedRow.id);
|
|
149
|
+
break;
|
|
117
150
|
}
|
|
118
151
|
activePicker.closePicker();
|
|
119
152
|
};
|
|
@@ -121,6 +154,7 @@ export function usePickerManager({ tableHeight, availableRegions, availableProfi
|
|
|
121
154
|
region: regionEntry,
|
|
122
155
|
profile: profileEntry,
|
|
123
156
|
resource: resourceEntry,
|
|
157
|
+
theme: themeEntry,
|
|
124
158
|
activePicker,
|
|
125
159
|
openPicker,
|
|
126
160
|
closeActivePicker,
|
package/dist/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { Command, Option } from "@commander-js/extra-typings";
|
|
|
4
4
|
import { App } from "./App.js";
|
|
5
5
|
import { SERVICE_REGISTRY } from "./services.js";
|
|
6
6
|
import { withFullscreen } from "./utils/withFullscreen.js";
|
|
7
|
+
import { ThemeProvider } from "./contexts/ThemeContext.js";
|
|
7
8
|
const SERVICE_IDS = Object.keys(SERVICE_REGISTRY);
|
|
8
9
|
const program = new Command()
|
|
9
10
|
.name("a9s")
|
|
@@ -17,7 +18,7 @@ program.parse();
|
|
|
17
18
|
// opts() return type is fully inferred from addOption() calls via extra-typings
|
|
18
19
|
const options = program.opts();
|
|
19
20
|
void (async () => {
|
|
20
|
-
const { instance, cleanup } = withFullscreen(_jsx(App, { initialService: options.service, endpointUrl: options.endpointUrl }));
|
|
21
|
+
const { instance, cleanup } = withFullscreen(_jsx(ThemeProvider, { children: _jsx(App, { initialService: options.service, endpointUrl: options.endpointUrl }) }));
|
|
21
22
|
process.on("SIGINT", () => {
|
|
22
23
|
cleanup();
|
|
23
24
|
process.exit(0);
|
package/dist/src/state/atoms.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { atom } from "jotai";
|
|
2
|
+
import { loadConfig } from "../utils/config.js";
|
|
2
3
|
/** Persists across HMR / re-renders. Currently selected AWS service. */
|
|
3
4
|
export const currentlySelectedServiceAtom = atom("s3");
|
|
4
5
|
/** Current UI mode (navigate / search / command). */
|
|
@@ -25,3 +26,5 @@ export const adapterSessionAtom = atom((get) => {
|
|
|
25
26
|
});
|
|
26
27
|
/** Toggle state for revealing/hiding secret values. Persists across HMR. */
|
|
27
28
|
export const revealSecretsAtom = atom(false);
|
|
29
|
+
/** Active UI theme name — initialized from ~/.config/a9s/config.json on startup. */
|
|
30
|
+
export const themeNameAtom = atom(loadConfig().theme ?? "monokai");
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import os from "os";
|
|
4
|
+
import * as YAML from "yaml";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { THEMES } from "../constants/theme.js";
|
|
7
|
+
const CONFIG_DIR = path.join(os.homedir(), ".config", "a9s");
|
|
8
|
+
const CONFIG_PATH = path.join(CONFIG_DIR, "config.yaml");
|
|
9
|
+
const THEME_NAMES = Object.keys(THEMES);
|
|
10
|
+
const A9sConfigSchema = z.object({
|
|
11
|
+
theme: z.enum(THEME_NAMES).optional(),
|
|
12
|
+
});
|
|
13
|
+
export function loadConfig() {
|
|
14
|
+
try {
|
|
15
|
+
const raw = YAML.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
16
|
+
return A9sConfigSchema.parse(raw);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function saveConfig(update) {
|
|
23
|
+
try {
|
|
24
|
+
let existing = {};
|
|
25
|
+
try {
|
|
26
|
+
const raw = YAML.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
|
|
27
|
+
existing = A9sConfigSchema.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch { }
|
|
30
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
31
|
+
fs.writeFileSync(CONFIG_PATH, YAML.stringify({ ...existing, ...update }));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Best-effort — silently ignore write failures
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -5,6 +5,7 @@ import { getDefaultStore } from "jotai";
|
|
|
5
5
|
import { unwrapDynamoValue, formatBillingMode, formatDynamoValue, getDynamoType, extractPkValue, extractSkValue, } from "./utils.js";
|
|
6
6
|
import { createDynamoDBDetailCapability } from "./capabilities/detailCapability.js";
|
|
7
7
|
import { createDynamoDBYankCapability } from "./capabilities/yankCapability.js";
|
|
8
|
+
import { SERVICE_COLORS } from "../../constants/theme.js";
|
|
8
9
|
export const dynamoDBLevelAtom = atom({ kind: "tables" });
|
|
9
10
|
export const dynamoDBBackStackAtom = atom([]);
|
|
10
11
|
// Cache for table descriptions to avoid repeated AWS calls
|
|
@@ -303,7 +304,7 @@ export function createDynamoDBServiceAdapter(endpointUrl, region) {
|
|
|
303
304
|
return {
|
|
304
305
|
id: "dynamodb",
|
|
305
306
|
label: "DynamoDB",
|
|
306
|
-
hudColor:
|
|
307
|
+
hudColor: SERVICE_COLORS.dynamodb,
|
|
307
308
|
getColumns,
|
|
308
309
|
getRows,
|
|
309
310
|
onSelect,
|
|
@@ -4,6 +4,7 @@ import { formatDate } from "./utils.js";
|
|
|
4
4
|
import { createIamEditCapability } from "./capabilities/editCapability.js";
|
|
5
5
|
import { createIamDetailCapability } from "./capabilities/detailCapability.js";
|
|
6
6
|
import { createIamYankCapability } from "./capabilities/yankCapability.js";
|
|
7
|
+
import { SERVICE_COLORS } from "../../constants/theme.js";
|
|
7
8
|
function getIamMeta(row) {
|
|
8
9
|
return row.meta;
|
|
9
10
|
}
|
|
@@ -241,7 +242,7 @@ export function createIamServiceAdapter() {
|
|
|
241
242
|
return {
|
|
242
243
|
id: "iam",
|
|
243
244
|
label: "IAM",
|
|
244
|
-
hudColor:
|
|
245
|
+
hudColor: SERVICE_COLORS.iam,
|
|
245
246
|
getColumns,
|
|
246
247
|
getRows,
|
|
247
248
|
onSelect,
|
|
@@ -4,6 +4,7 @@ import { atom } from "jotai";
|
|
|
4
4
|
import { getDefaultStore } from "jotai";
|
|
5
5
|
import { createRoute53DetailCapability } from "./capabilities/detailCapability.js";
|
|
6
6
|
import { createRoute53YankCapability } from "./capabilities/yankCapability.js";
|
|
7
|
+
import { SERVICE_COLORS } from "../../constants/theme.js";
|
|
7
8
|
export const route53LevelAtom = atom({ kind: "zones" });
|
|
8
9
|
export const route53BackStackAtom = atom([]);
|
|
9
10
|
export function createRoute53ServiceAdapter(endpointUrl, region) {
|
|
@@ -158,7 +159,7 @@ export function createRoute53ServiceAdapter(endpointUrl, region) {
|
|
|
158
159
|
return {
|
|
159
160
|
id: "route53",
|
|
160
161
|
label: "Route53",
|
|
161
|
-
hudColor:
|
|
162
|
+
hudColor: SERVICE_COLORS.route53,
|
|
162
163
|
getColumns,
|
|
163
164
|
getRows,
|
|
164
165
|
onSelect,
|
|
@@ -8,6 +8,7 @@ import { createS3EditCapability } from "./capabilities/editCapability.js";
|
|
|
8
8
|
import { createS3DetailCapability } from "./capabilities/detailCapability.js";
|
|
9
9
|
import { createS3YankCapability } from "./capabilities/yankCapability.js";
|
|
10
10
|
import { createS3ActionCapability } from "./capabilities/actionCapability.js";
|
|
11
|
+
import { SERVICE_COLORS } from "../../constants/theme.js";
|
|
11
12
|
export const s3LevelAtom = atom({ kind: "buckets" });
|
|
12
13
|
export const s3BackStackAtom = atom([]);
|
|
13
14
|
export function createS3ServiceAdapter(endpointUrl, region) {
|
|
@@ -132,7 +133,7 @@ export function createS3ServiceAdapter(endpointUrl, region) {
|
|
|
132
133
|
return {
|
|
133
134
|
id: "s3",
|
|
134
135
|
label: "S3",
|
|
135
|
-
hudColor:
|
|
136
|
+
hudColor: SERVICE_COLORS.s3,
|
|
136
137
|
getColumns,
|
|
137
138
|
getRows,
|
|
138
139
|
onSelect,
|
|
@@ -7,6 +7,7 @@ import { createSecretsManagerDetailCapability } from "./capabilities/detailCapab
|
|
|
7
7
|
import { createSecretsManagerYankCapability } from "./capabilities/yankCapability.js";
|
|
8
8
|
import { createSecretsManagerActionCapability } from "./capabilities/actionCapability.js";
|
|
9
9
|
import { createSecretsManagerEditCapability } from "./capabilities/editCapability.js";
|
|
10
|
+
import { SERVICE_COLORS } from "../../constants/theme.js";
|
|
10
11
|
export const secretLevelAtom = atom({ kind: "secrets" });
|
|
11
12
|
export const secretBackStackAtom = atom([]);
|
|
12
13
|
function tryParseFields(secretString) {
|
|
@@ -166,7 +167,7 @@ export function createSecretsManagerServiceAdapter(endpointUrl, region) {
|
|
|
166
167
|
return {
|
|
167
168
|
id: "secretsmanager",
|
|
168
169
|
label: "Secrets Manager",
|
|
169
|
-
hudColor:
|
|
170
|
+
hudColor: SERVICE_COLORS.secretsmanager,
|
|
170
171
|
getColumns,
|
|
171
172
|
getRows,
|
|
172
173
|
onSelect,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a9s/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"description": "k9s-style TUI navigator for AWS services",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"aws",
|
|
@@ -58,6 +58,7 @@
|
|
|
58
58
|
"open": "^11.0.0",
|
|
59
59
|
"open-editor": "^6.0.0",
|
|
60
60
|
"react": "^19.2.4",
|
|
61
|
+
"yaml": "^2.8.2",
|
|
61
62
|
"zod": "^4.3.6"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|