@lkangd/cc-env 1.2.1 → 1.3.1
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 +4 -0
- package/README.zh.md +4 -0
- package/dist/cli.js +109 -80
- package/dist/commands/completion.js +24 -18
- package/dist/commands/doctor.js +2 -1
- package/dist/commands/init.js +2 -9
- package/dist/commands/preset/create.js +70 -9
- package/dist/commands/restore.js +25 -1
- package/dist/commands/run.js +27 -22
- package/dist/core/claude-required-keys.js +8 -0
- package/dist/core/cli-name.js +8 -0
- package/dist/core/schema.js +10 -0
- package/dist/flows/preset-create-flow.js +49 -1
- package/dist/flows/restore-flow.js +26 -7
- package/dist/ink/components/text-input.js +7 -0
- package/dist/ink/hooks/use-text-input.js +71 -0
- package/dist/ink/preset-create-app.js +92 -43
- package/dist/ink/preset-edit-app.js +12 -16
- package/dist/ink/restore-app.js +24 -9
- package/package.json +3 -2
- package/dist/ink/init-app.js +0 -54
package/dist/commands/run.js
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
|
-
import { CliError } from '../core/errors.js';
|
|
2
1
|
import { formatRunEnvBlock } from '../core/format.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
];
|
|
11
|
-
export function createRunCommand({ claudeSettingsEnvService, presetService, projectEnvService, projectStateService, findClaude, renderPresetSelect, spawnCommand, stdout = process.stdout, }) {
|
|
12
|
-
return async function run({ args = [], dryRun = false, yes = false, json = false, cwd, }) {
|
|
13
|
-
// Step 0: Check settings files for init-managed keys
|
|
2
|
+
import { requiredClaudeKeys } from '../core/claude-required-keys.js';
|
|
3
|
+
const detectTriggerKeys = ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL'];
|
|
4
|
+
function getDetectedEnv(sources) {
|
|
5
|
+
return sources.reduce((acc, source) => ({ ...acc, ...source.env }), {});
|
|
6
|
+
}
|
|
7
|
+
export function createRunCommand({ claudeSettingsEnvService, presetService, projectEnvService, projectStateService, findClaude, renderPresetSelect, spawnCommand, stdout = process.stdout }) {
|
|
8
|
+
return async function run({ args = [], dryRun = false, yes = false, json = false, skipDetect = false, cwd }) {
|
|
14
9
|
const sources = await claudeSettingsEnvService.read();
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
const detectedEnv = getDetectedEnv(sources);
|
|
11
|
+
const requiredKeys = requiredClaudeKeys.filter((key) => key in detectedEnv);
|
|
12
|
+
const hasDetectTrigger = detectTriggerKeys.some((key) => key in detectedEnv);
|
|
13
|
+
if (!skipDetect && hasDetectTrigger) {
|
|
14
|
+
return {
|
|
15
|
+
status: 'needs-preset',
|
|
16
|
+
detectedEnv,
|
|
17
|
+
requiredKeys,
|
|
18
|
+
};
|
|
19
19
|
}
|
|
20
20
|
// Step 1: Collect all presets (project + global)
|
|
21
21
|
const names = await presetService.listNames();
|
|
22
|
-
const globalPresets = await Promise.all(names.map(
|
|
22
|
+
const globalPresets = await Promise.all(names.map(name => presetService.read(name).then(p => ({ name, env: p.env, source: 'global' }))));
|
|
23
23
|
const { env: projectEnv, name: projectName } = await projectEnvService.readWithMeta();
|
|
24
24
|
const projectPreset = Object.keys(projectEnv).length > 0
|
|
25
25
|
? [{ name: projectName ?? 'project', env: projectEnv, source: 'project' }]
|
|
26
26
|
: [];
|
|
27
27
|
const presets = [...projectPreset, ...globalPresets];
|
|
28
28
|
if (presets.length === 0) {
|
|
29
|
-
|
|
29
|
+
return {
|
|
30
|
+
status: 'needs-preset',
|
|
31
|
+
detectedEnv,
|
|
32
|
+
requiredKeys,
|
|
33
|
+
};
|
|
30
34
|
}
|
|
31
35
|
// Step 2: Determine default selection
|
|
32
36
|
const savedRef = await projectStateService.getLastPreset(cwd);
|
|
33
37
|
let defaultIndex = 0;
|
|
34
38
|
if (savedRef) {
|
|
35
|
-
const idx = presets.findIndex(
|
|
39
|
+
const idx = presets.findIndex(p => p.name === savedRef.presetName && p.source === savedRef.source);
|
|
36
40
|
if (idx >= 0)
|
|
37
41
|
defaultIndex = idx;
|
|
38
42
|
}
|
|
@@ -52,7 +56,7 @@ export function createRunCommand({ claudeSettingsEnvService, presetService, proj
|
|
|
52
56
|
// Step 4: Save selection
|
|
53
57
|
await projectStateService.saveLastPreset(cwd, {
|
|
54
58
|
presetName: selected.name,
|
|
55
|
-
source: selected.source
|
|
59
|
+
source: selected.source
|
|
56
60
|
});
|
|
57
61
|
// Step 5: Resolve claude command
|
|
58
62
|
let command;
|
|
@@ -72,7 +76,7 @@ export function createRunCommand({ claudeSettingsEnvService, presetService, proj
|
|
|
72
76
|
command: [command, ...claudeArgs],
|
|
73
77
|
env: selected.env
|
|
74
78
|
}, null, 2) + '\n');
|
|
75
|
-
return;
|
|
79
|
+
return { status: 'executed' };
|
|
76
80
|
}
|
|
77
81
|
const presetKeys = new Set(Object.keys(selected.env));
|
|
78
82
|
const envBlock = formatRunEnvBlock(selected.env, presetKeys);
|
|
@@ -80,9 +84,10 @@ export function createRunCommand({ claudeSettingsEnvService, presetService, proj
|
|
|
80
84
|
if (dryRun) {
|
|
81
85
|
const preview = [command, ...claudeArgs].join(' ');
|
|
82
86
|
stdout.write(`Would run: ${preview}\n`);
|
|
83
|
-
return;
|
|
87
|
+
return { status: 'executed' };
|
|
84
88
|
}
|
|
85
89
|
// Step 7: Spawn
|
|
86
90
|
await spawnCommand(command, claudeArgs, { ...process.env, ...selected.env });
|
|
91
|
+
return { status: 'executed' };
|
|
87
92
|
};
|
|
88
93
|
}
|
package/dist/core/schema.js
CHANGED
|
@@ -32,7 +32,17 @@ const restoreHistorySchema = z.object({
|
|
|
32
32
|
targetType: z.enum(['settings', 'preset']),
|
|
33
33
|
targetName: z.string(),
|
|
34
34
|
});
|
|
35
|
+
const presetCreateHistorySchema = z.object({
|
|
36
|
+
timestamp: z.string().datetime({ offset: true }),
|
|
37
|
+
action: z.literal('preset-create'),
|
|
38
|
+
projectPath: z.string(),
|
|
39
|
+
presetName: z.string(),
|
|
40
|
+
destination: z.enum(['global', 'project']),
|
|
41
|
+
migratedKeys: z.array(envKeySchema),
|
|
42
|
+
sources: z.array(sourceEntrySchema),
|
|
43
|
+
});
|
|
35
44
|
export const historySchema = z.discriminatedUnion('action', [
|
|
36
45
|
initHistorySchema,
|
|
37
46
|
restoreHistorySchema,
|
|
47
|
+
presetCreateHistorySchema,
|
|
38
48
|
]);
|
|
@@ -1,14 +1,62 @@
|
|
|
1
|
-
export function createPresetCreateFlowState() {
|
|
1
|
+
export function createPresetCreateFlowState(input) {
|
|
2
|
+
const detectedEnv = input?.detectedEnv ?? {};
|
|
3
|
+
const requiredKeys = input?.requiredKeys ?? [];
|
|
4
|
+
const detectedKeys = Object.keys(detectedEnv).sort();
|
|
5
|
+
const selectedKeys = requiredKeys.filter((key) => key in detectedEnv);
|
|
6
|
+
if (selectedKeys.length > 0) {
|
|
7
|
+
return {
|
|
8
|
+
step: 'detectedPrompt',
|
|
9
|
+
env: detectedEnv,
|
|
10
|
+
allKeys: detectedKeys,
|
|
11
|
+
selectedKeys,
|
|
12
|
+
requiredKeys: selectedKeys,
|
|
13
|
+
presetName: '',
|
|
14
|
+
};
|
|
15
|
+
}
|
|
2
16
|
return {
|
|
3
17
|
step: 'source',
|
|
4
18
|
env: {},
|
|
5
19
|
allKeys: [],
|
|
6
20
|
selectedKeys: [],
|
|
21
|
+
requiredKeys: [],
|
|
7
22
|
presetName: '',
|
|
8
23
|
};
|
|
9
24
|
}
|
|
10
25
|
export function advancePresetCreateFlow(state, action) {
|
|
11
26
|
switch (state.step) {
|
|
27
|
+
case 'detectedPrompt':
|
|
28
|
+
if (action.type === 'accept-detected-prompt') {
|
|
29
|
+
return {
|
|
30
|
+
...state,
|
|
31
|
+
step: 'detected',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (action.type === 'reject-detected-prompt') {
|
|
35
|
+
const { source: _source, ...rest } = state;
|
|
36
|
+
return {
|
|
37
|
+
...rest,
|
|
38
|
+
step: 'source',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return state;
|
|
42
|
+
case 'detected':
|
|
43
|
+
if (action.type === 'toggle-detected-key') {
|
|
44
|
+
if (state.requiredKeys.includes(action.key) || !state.allKeys.includes(action.key)) {
|
|
45
|
+
return state;
|
|
46
|
+
}
|
|
47
|
+
const selectedKeys = state.selectedKeys.includes(action.key)
|
|
48
|
+
? state.selectedKeys.filter((key) => key !== action.key)
|
|
49
|
+
: [...state.selectedKeys, action.key].sort();
|
|
50
|
+
return { ...state, selectedKeys };
|
|
51
|
+
}
|
|
52
|
+
if (action.type === 'confirm-detected-keys') {
|
|
53
|
+
return {
|
|
54
|
+
...state,
|
|
55
|
+
step: 'name',
|
|
56
|
+
source: 'detected',
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return state;
|
|
12
60
|
case 'source':
|
|
13
61
|
if (action.type !== 'select-source')
|
|
14
62
|
return state;
|
|
@@ -1,7 +1,26 @@
|
|
|
1
|
-
export function createRestoreFlowState(records) {
|
|
1
|
+
export function createRestoreFlowState(records, cwd) {
|
|
2
|
+
const currentProjectRecords = records
|
|
3
|
+
.filter((record) => 'projectPath' in record && record.projectPath === cwd)
|
|
4
|
+
.sort((left, right) => right.timestamp.localeCompare(left.timestamp));
|
|
5
|
+
const otherHistoryRecords = records
|
|
6
|
+
.filter((record) => !('projectPath' in record) || record.projectPath !== cwd)
|
|
7
|
+
.sort((left, right) => right.timestamp.localeCompare(left.timestamp));
|
|
8
|
+
const orderedRecords = [...currentProjectRecords, ...otherHistoryRecords];
|
|
9
|
+
const groups = [];
|
|
10
|
+
if (currentProjectRecords.length > 0) {
|
|
11
|
+
groups.push({ title: 'Current project', start: 0, end: currentProjectRecords.length });
|
|
12
|
+
}
|
|
13
|
+
if (otherHistoryRecords.length > 0) {
|
|
14
|
+
groups.push({
|
|
15
|
+
title: 'Other history',
|
|
16
|
+
start: currentProjectRecords.length,
|
|
17
|
+
end: orderedRecords.length,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
2
20
|
return {
|
|
3
21
|
step: 'record',
|
|
4
|
-
records,
|
|
22
|
+
records: orderedRecords,
|
|
23
|
+
groups,
|
|
5
24
|
};
|
|
6
25
|
}
|
|
7
26
|
export function advanceRestoreFlow(state, action) {
|
|
@@ -14,7 +33,7 @@ export function advanceRestoreFlow(state, action) {
|
|
|
14
33
|
if (!selectedRecord) {
|
|
15
34
|
return state;
|
|
16
35
|
}
|
|
17
|
-
if (selectedRecord.action === 'init') {
|
|
36
|
+
if (selectedRecord.action === 'init' || selectedRecord.action === 'preset-create') {
|
|
18
37
|
return {
|
|
19
38
|
...state,
|
|
20
39
|
step: 'confirm',
|
|
@@ -41,19 +60,18 @@ export function advanceRestoreFlow(state, action) {
|
|
|
41
60
|
targetType: 'settings',
|
|
42
61
|
};
|
|
43
62
|
}
|
|
44
|
-
const targetName = action.targetName;
|
|
45
63
|
return {
|
|
46
64
|
...state,
|
|
47
65
|
step: 'confirm',
|
|
48
66
|
targetType: 'preset',
|
|
49
|
-
targetName,
|
|
67
|
+
targetName: action.targetName,
|
|
50
68
|
};
|
|
51
|
-
case 'confirm':
|
|
69
|
+
case 'confirm': {
|
|
52
70
|
if (action.type !== 'confirm' || !state.selectedTimestamp) {
|
|
53
71
|
return state;
|
|
54
72
|
}
|
|
55
73
|
const selectedRecord = state.records.find((record) => record.timestamp === state.selectedTimestamp);
|
|
56
|
-
if (selectedRecord?.action === 'init') {
|
|
74
|
+
if (selectedRecord?.action === 'init' || selectedRecord?.action === 'preset-create') {
|
|
57
75
|
return {
|
|
58
76
|
...state,
|
|
59
77
|
step: 'done',
|
|
@@ -69,6 +87,7 @@ export function advanceRestoreFlow(state, action) {
|
|
|
69
87
|
...state,
|
|
70
88
|
step: 'done',
|
|
71
89
|
};
|
|
90
|
+
}
|
|
72
91
|
case 'done':
|
|
73
92
|
return state;
|
|
74
93
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
export function TextInputDisplay({ value, cursorPos }) {
|
|
4
|
+
const before = value.slice(0, cursorPos);
|
|
5
|
+
const after = value.slice(cursorPos);
|
|
6
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { dimColor: true, children: ['>', " "] }), _jsx(Text, { color: "cyan", children: before }), _jsx(Text, { dimColor: true, children: "\u2588" }), _jsx(Text, { color: "cyan", children: after })] }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
export function handleKey(state, input, key, setState) {
|
|
3
|
+
const { value, cursorPos } = state;
|
|
4
|
+
if (key.leftArrow) {
|
|
5
|
+
if (cursorPos > 0)
|
|
6
|
+
setState(value, cursorPos - 1);
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
if (key.rightArrow) {
|
|
10
|
+
if (cursorPos < value.length)
|
|
11
|
+
setState(value, cursorPos + 1);
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (key.home) {
|
|
15
|
+
setState(value, 0);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (key.end) {
|
|
19
|
+
setState(value, value.length);
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (key.ctrl && input === 'a') {
|
|
23
|
+
setState(value, 0);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (key.ctrl && input === 'e') {
|
|
27
|
+
setState(value, value.length);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (key.ctrl && input === 'u') {
|
|
31
|
+
setState(value.slice(cursorPos), 0);
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
if (key.ctrl && input === 'k') {
|
|
35
|
+
setState(value.slice(0, cursorPos), cursorPos);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if ((key.ctrl && (key.backspace || key.delete)) || (key.meta && (key.backspace || key.delete))) {
|
|
39
|
+
if (cursorPos > 0) {
|
|
40
|
+
setState(value.slice(cursorPos), 0);
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (key.backspace || key.delete) {
|
|
45
|
+
if (cursorPos > 0) {
|
|
46
|
+
setState(value.slice(0, cursorPos - 1) + value.slice(cursorPos), cursorPos - 1);
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (input && !key.ctrl && !key.meta) {
|
|
51
|
+
setState(value.slice(0, cursorPos) + input + value.slice(cursorPos), cursorPos + 1);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
export function useTextInput() {
|
|
57
|
+
const [value, setValue] = useState('');
|
|
58
|
+
const [cursorPos, setCursorPos] = useState(0);
|
|
59
|
+
const setState = (newValue, newCursor) => {
|
|
60
|
+
setValue(newValue);
|
|
61
|
+
setCursorPos(newCursor);
|
|
62
|
+
};
|
|
63
|
+
const onKey = (input, key) => {
|
|
64
|
+
return handleKey({ value, cursorPos }, input, key, setState);
|
|
65
|
+
};
|
|
66
|
+
const reset = (newValue = '') => {
|
|
67
|
+
setValue(newValue);
|
|
68
|
+
setCursorPos(newValue.length);
|
|
69
|
+
};
|
|
70
|
+
return { value, cursorPos, handleKey: onKey, setValue, reset };
|
|
71
|
+
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
|
+
import { useTextInput } from './hooks/use-text-input.js';
|
|
5
|
+
import { TextInputDisplay } from './components/text-input.js';
|
|
4
6
|
import { advancePresetCreateFlow, createPresetCreateFlowState, } from '../flows/preset-create-flow.js';
|
|
5
7
|
import { EnvSummary } from './summary.js';
|
|
8
|
+
function DetectedPromptStep({ cursor }) {
|
|
9
|
+
const options = ['Generate from detected config', 'Choose another source'];
|
|
10
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Detected existing configuration" }), _jsx(Text, { dimColor: true, children: "Use the currently detected settings to generate a preset?" }), _jsx(Text, { dimColor: true, children: "\u2191/k \u2193/j navigate \u00B7 enter confirm \u00B7 q cancel" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: options.map((label, i) => (_jsxs(Box, { children: [_jsx(Text, { children: i === cursor ? '❯ ' : ' ' }), _jsx(Text, { ...(i === cursor ? { color: 'cyan' } : {}), children: label })] }, label))) })] }));
|
|
11
|
+
}
|
|
12
|
+
function DetectedKeysStep({ keys, selectedKeys, requiredKeys, cursor, }) {
|
|
13
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Select detected env keys to migrate" }), _jsx(Text, { dimColor: true, children: "\u2191/k \u2193/j navigate \u00B7 space toggle optional keys \u00B7 enter confirm" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: keys.map((key, i) => {
|
|
14
|
+
const isSelected = selectedKeys.includes(key);
|
|
15
|
+
const isRequired = requiredKeys.includes(key);
|
|
16
|
+
return (_jsxs(Box, { children: [_jsx(Text, { children: i === cursor ? '❯ ' : ' ' }), _jsx(Text, { color: isSelected ? 'green' : '', children: isSelected ? '[x]' : '[ ]' }), _jsx(Text, { children: isRequired ? ' ! ' : ' ' }), _jsxs(Text, { children: [" ", key] })] }, key));
|
|
17
|
+
}) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "! required key \u00B7 q cancel" }) })] }));
|
|
18
|
+
}
|
|
6
19
|
function SourceStep({ cursor }) {
|
|
7
20
|
const options = [
|
|
8
21
|
{ label: 'File import', value: 'file' },
|
|
@@ -10,8 +23,8 @@ function SourceStep({ cursor }) {
|
|
|
10
23
|
];
|
|
11
24
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Select env source" }), _jsx(Text, { dimColor: true, children: "\u2191/k \u2193/j navigate \u00B7 enter confirm" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: options.map((opt, i) => (_jsxs(Box, { children: [_jsx(Text, { children: i === cursor ? '❯ ' : ' ' }), _jsx(Text, { ...(i === cursor ? { color: 'cyan' } : {}), children: opt.label })] }, opt.value))) })] }));
|
|
12
25
|
}
|
|
13
|
-
function FilePathStep({ value, error }) {
|
|
14
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Enter file path (.yaml/.yml/.json)" }),
|
|
26
|
+
function FilePathStep({ value, cursorPos, error }) {
|
|
27
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Enter file path (.yaml/.yml/.json)" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInputDisplay, { value: value, cursorPos: cursorPos }) }), error ? _jsx(Text, { color: "red", children: error }) : null] }));
|
|
15
28
|
}
|
|
16
29
|
function KeysStep({ keys, selectedKeys, cursor, }) {
|
|
17
30
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Select env keys to import" }), _jsx(Text, { dimColor: true, children: "\u2191/k \u2193/j navigate \u00B7 space toggle \u00B7 enter confirm" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: keys.map((key, i) => {
|
|
@@ -19,11 +32,11 @@ function KeysStep({ keys, selectedKeys, cursor, }) {
|
|
|
19
32
|
return (_jsxs(Box, { children: [_jsx(Text, { children: i === cursor ? '❯ ' : ' ' }), _jsx(Text, { color: isSelected ? 'green' : '', children: isSelected ? '[x]' : '[ ]' }), _jsxs(Text, { children: [" ", key] })] }, key));
|
|
20
33
|
}) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: [selectedKeys.length, " of ", keys.length, " selected"] }) })] }));
|
|
21
34
|
}
|
|
22
|
-
function ManualInputStep({ entries, value, error, }) {
|
|
23
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Enter KEY=VALUE pairs (press q when done)" }), entries.length > 0 ? (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: entries.map(([key, val]) => (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "\u2022 " }), _jsx(Text, { color: "magenta", children: key }), _jsx(Text, { dimColor: true, children: "=" }), _jsx(Text, { children: val })] }, key))) })) : null,
|
|
35
|
+
function ManualInputStep({ entries, value, cursorPos, error, }) {
|
|
36
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Enter KEY=VALUE pairs (press q when done)" }), entries.length > 0 ? (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: entries.map(([key, val]) => (_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "\u2022 " }), _jsx(Text, { color: "magenta", children: key }), _jsx(Text, { dimColor: true, children: "=" }), _jsx(Text, { children: val })] }, key))) })) : null, _jsx(TextInputDisplay, { value: value, cursorPos: cursorPos }), error ? _jsx(Text, { color: "red", children: error }) : null] }));
|
|
24
37
|
}
|
|
25
|
-
function NameStep({ value }) {
|
|
26
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Enter preset name" }),
|
|
38
|
+
function NameStep({ value, cursorPos }) {
|
|
39
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Enter preset name" }), _jsx(Box, { marginTop: 1, children: _jsx(TextInputDisplay, { value: value, cursorPos: cursorPos }) })] }));
|
|
27
40
|
}
|
|
28
41
|
function DestinationStep({ cursor }) {
|
|
29
42
|
const options = [
|
|
@@ -32,10 +45,12 @@ function DestinationStep({ cursor }) {
|
|
|
32
45
|
];
|
|
33
46
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Select save destination" }), _jsx(Text, { dimColor: true, children: "\u2191/k \u2193/j navigate \u00B7 enter confirm" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: options.map((opt, i) => (_jsxs(Box, { children: [_jsx(Text, { children: i === cursor ? '❯ ' : ' ' }), _jsx(Text, { ...(i === cursor ? { color: 'cyan' } : {}), children: opt.label })] }, opt.value))) })] }));
|
|
34
47
|
}
|
|
35
|
-
export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectEnvPath, }) {
|
|
48
|
+
export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectEnvPath, detectedEnv, requiredKeys, }) {
|
|
36
49
|
const { exit } = useApp();
|
|
37
|
-
const [state, setState] = useState(createPresetCreateFlowState
|
|
38
|
-
|
|
50
|
+
const [state, setState] = useState(() => createPresetCreateFlowState(detectedEnv
|
|
51
|
+
? (requiredKeys ? { detectedEnv, requiredKeys } : { detectedEnv })
|
|
52
|
+
: undefined));
|
|
53
|
+
const textInput = useTextInput();
|
|
39
54
|
const [listCursor, setListCursor] = useState(0);
|
|
40
55
|
const [allKeys, setAllKeys] = useState([]);
|
|
41
56
|
const [fileEnv, setFileEnv] = useState({});
|
|
@@ -44,6 +59,58 @@ export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectE
|
|
|
44
59
|
exit();
|
|
45
60
|
return;
|
|
46
61
|
}
|
|
62
|
+
if (state.step === 'detectedPrompt') {
|
|
63
|
+
if (input === 'q') {
|
|
64
|
+
exit();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (key.upArrow || input === 'k') {
|
|
68
|
+
setListCursor((c) => Math.max(0, c - 1));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (key.downArrow || input === 'j') {
|
|
72
|
+
setListCursor((c) => Math.min(1, c + 1));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (key.return) {
|
|
76
|
+
setState((s) => advancePresetCreateFlow(s, listCursor === 0
|
|
77
|
+
? { type: 'accept-detected-prompt' }
|
|
78
|
+
: { type: 'reject-detected-prompt' }));
|
|
79
|
+
setListCursor(0);
|
|
80
|
+
textInput.reset();
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (state.step === 'detected') {
|
|
85
|
+
if (input === 'q') {
|
|
86
|
+
exit();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (key.upArrow || input === 'k') {
|
|
90
|
+
setListCursor((c) => Math.max(0, c - 1));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (key.downArrow || input === 'j') {
|
|
94
|
+
setListCursor((c) => Math.min(state.allKeys.length - 1, c + 1));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
if (input === ' ') {
|
|
98
|
+
const targetKey = state.allKeys[listCursor];
|
|
99
|
+
if (targetKey) {
|
|
100
|
+
setState((s) => advancePresetCreateFlow(s, {
|
|
101
|
+
type: 'toggle-detected-key',
|
|
102
|
+
key: targetKey,
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (key.return) {
|
|
108
|
+
setState((s) => advancePresetCreateFlow(s, { type: 'confirm-detected-keys' }));
|
|
109
|
+
setListCursor(0);
|
|
110
|
+
textInput.reset();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
47
114
|
if (state.step === 'source') {
|
|
48
115
|
if (input === 'q') {
|
|
49
116
|
exit();
|
|
@@ -61,7 +128,7 @@ export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectE
|
|
|
61
128
|
const source = listCursor === 0 ? 'file' : 'manual';
|
|
62
129
|
setState((s) => advancePresetCreateFlow(s, { type: 'select-source', source }));
|
|
63
130
|
setListCursor(0);
|
|
64
|
-
|
|
131
|
+
textInput.reset();
|
|
65
132
|
return;
|
|
66
133
|
}
|
|
67
134
|
}
|
|
@@ -70,14 +137,10 @@ export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectE
|
|
|
70
137
|
exit();
|
|
71
138
|
return;
|
|
72
139
|
}
|
|
73
|
-
if (key.backspace || key.delete) {
|
|
74
|
-
setTextInput((v) => v.slice(0, -1));
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
140
|
if (key.return) {
|
|
78
141
|
void (async () => {
|
|
79
142
|
try {
|
|
80
|
-
const result = await readFile(textInput);
|
|
143
|
+
const result = await readFile(textInput.value);
|
|
81
144
|
if (result.allKeys.length === 0) {
|
|
82
145
|
setState((s) => advancePresetCreateFlow(s, {
|
|
83
146
|
type: 'set-error',
|
|
@@ -89,7 +152,7 @@ export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectE
|
|
|
89
152
|
setFileEnv(result.env);
|
|
90
153
|
setState((s) => advancePresetCreateFlow(s, {
|
|
91
154
|
type: 'set-file-path',
|
|
92
|
-
filePath: textInput,
|
|
155
|
+
filePath: textInput.value,
|
|
93
156
|
}));
|
|
94
157
|
setListCursor(0);
|
|
95
158
|
}
|
|
@@ -103,10 +166,8 @@ export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectE
|
|
|
103
166
|
})();
|
|
104
167
|
return;
|
|
105
168
|
}
|
|
106
|
-
if (input
|
|
107
|
-
setTextInput((v) => v + input);
|
|
169
|
+
if (textInput.handleKey(input, key))
|
|
108
170
|
return;
|
|
109
|
-
}
|
|
110
171
|
}
|
|
111
172
|
if (state.step === 'keys') {
|
|
112
173
|
if (input === 'q') {
|
|
@@ -141,12 +202,12 @@ export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectE
|
|
|
141
202
|
keys: state.selectedKeys,
|
|
142
203
|
env: selectedEnv,
|
|
143
204
|
}));
|
|
144
|
-
|
|
205
|
+
textInput.reset();
|
|
145
206
|
return;
|
|
146
207
|
}
|
|
147
208
|
}
|
|
148
209
|
if (state.step === 'manualInput') {
|
|
149
|
-
if (input === 'q' && textInput === '') {
|
|
210
|
+
if (input === 'q' && textInput.value === '') {
|
|
150
211
|
if (state.selectedKeys.length === 0) {
|
|
151
212
|
setState((s) => advancePresetCreateFlow(s, {
|
|
152
213
|
type: 'set-error',
|
|
@@ -155,15 +216,11 @@ export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectE
|
|
|
155
216
|
return;
|
|
156
217
|
}
|
|
157
218
|
setState((s) => advancePresetCreateFlow(s, { type: 'finish-manual-input' }));
|
|
158
|
-
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
if (key.backspace || key.delete) {
|
|
162
|
-
setTextInput((v) => v.slice(0, -1));
|
|
219
|
+
textInput.reset();
|
|
163
220
|
return;
|
|
164
221
|
}
|
|
165
222
|
if (key.return) {
|
|
166
|
-
const separatorIndex = textInput.indexOf('=');
|
|
223
|
+
const separatorIndex = textInput.value.indexOf('=');
|
|
167
224
|
if (separatorIndex <= 0) {
|
|
168
225
|
setState((s) => advancePresetCreateFlow(s, {
|
|
169
226
|
type: 'set-error',
|
|
@@ -171,8 +228,8 @@ export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectE
|
|
|
171
228
|
}));
|
|
172
229
|
return;
|
|
173
230
|
}
|
|
174
|
-
const k = textInput.slice(0, separatorIndex);
|
|
175
|
-
const v = textInput.slice(separatorIndex + 1);
|
|
231
|
+
const k = textInput.value.slice(0, separatorIndex);
|
|
232
|
+
const v = textInput.value.slice(separatorIndex + 1);
|
|
176
233
|
if (!/^[A-Z0-9_]+$/.test(k)) {
|
|
177
234
|
setState((s) => advancePresetCreateFlow(s, {
|
|
178
235
|
type: 'set-error',
|
|
@@ -185,35 +242,27 @@ export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectE
|
|
|
185
242
|
key: k,
|
|
186
243
|
value: v,
|
|
187
244
|
}));
|
|
188
|
-
|
|
245
|
+
textInput.reset();
|
|
189
246
|
return;
|
|
190
247
|
}
|
|
191
|
-
if (input
|
|
192
|
-
setTextInput((v) => v + input);
|
|
248
|
+
if (textInput.handleKey(input, key))
|
|
193
249
|
return;
|
|
194
|
-
}
|
|
195
250
|
}
|
|
196
251
|
if (state.step === 'name') {
|
|
197
252
|
if (input === 'q') {
|
|
198
253
|
exit();
|
|
199
254
|
return;
|
|
200
255
|
}
|
|
201
|
-
if (key.
|
|
202
|
-
setTextInput((v) => v.slice(0, -1));
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (key.return && textInput.trim().length > 0) {
|
|
256
|
+
if (key.return && textInput.value.trim().length > 0) {
|
|
206
257
|
setState((s) => advancePresetCreateFlow(s, {
|
|
207
258
|
type: 'set-name',
|
|
208
|
-
name: textInput.trim(),
|
|
259
|
+
name: textInput.value.trim(),
|
|
209
260
|
}));
|
|
210
261
|
setListCursor(0);
|
|
211
262
|
return;
|
|
212
263
|
}
|
|
213
|
-
if (input
|
|
214
|
-
setTextInput((v) => v + input);
|
|
264
|
+
if (textInput.handleKey(input, key))
|
|
215
265
|
return;
|
|
216
|
-
}
|
|
217
266
|
}
|
|
218
267
|
if (state.step === 'destination') {
|
|
219
268
|
if (input === 'q') {
|
|
@@ -261,7 +310,7 @@ export function PresetCreateApp({ onSubmit, readFile, globalPresetPath, projectE
|
|
|
261
310
|
if (state.step === 'done') {
|
|
262
311
|
return (_jsx(Box, { flexDirection: "column", children: _jsx(Text, { color: "green", children: "Done" }) }));
|
|
263
312
|
}
|
|
264
|
-
return (_jsxs(Box, { flexDirection: "column", children: [state.step === 'source' && _jsx(SourceStep, { cursor: listCursor }), state.step === 'filePath' && (_jsx(FilePathStep, { value: textInput, ...(state.error ? { error: state.error } : {}) })), state.step === 'keys' && (_jsx(KeysStep, { keys: allKeys, selectedKeys: state.selectedKeys, cursor: listCursor })), state.step === 'manualInput' && (_jsx(ManualInputStep, { entries: state.selectedKeys.map((k) => [k, state.env[k] ?? '']), value: textInput, ...(state.error ? { error: state.error } : {}) })), state.step === 'name' && _jsx(NameStep, { value: textInput }), state.step === 'destination' && _jsx(DestinationStep, { cursor: listCursor }), state.step === 'confirm' && state.destination ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(EnvSummary, { title: `Preset: ${state.presetName}`, entries: Object.entries(state.env)
|
|
313
|
+
return (_jsxs(Box, { flexDirection: "column", children: [state.step === 'detectedPrompt' && _jsx(DetectedPromptStep, { cursor: listCursor }), state.step === 'detected' && (_jsx(DetectedKeysStep, { keys: state.allKeys, selectedKeys: state.selectedKeys, requiredKeys: state.requiredKeys, cursor: listCursor })), state.step === 'source' && _jsx(SourceStep, { cursor: listCursor }), state.step === 'filePath' && (_jsx(FilePathStep, { value: textInput.value, cursorPos: textInput.cursorPos, ...(state.error ? { error: state.error } : {}) })), state.step === 'keys' && (_jsx(KeysStep, { keys: allKeys, selectedKeys: state.selectedKeys, cursor: listCursor })), state.step === 'manualInput' && (_jsx(ManualInputStep, { entries: state.selectedKeys.map((k) => [k, state.env[k] ?? '']), value: textInput.value, cursorPos: textInput.cursorPos, ...(state.error ? { error: state.error } : {}) })), state.step === 'name' && _jsx(NameStep, { value: textInput.value, cursorPos: textInput.cursorPos }), state.step === 'destination' && _jsx(DestinationStep, { cursor: listCursor }), state.step === 'confirm' && state.destination ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(EnvSummary, { title: `Preset: ${state.presetName}`, entries: Object.entries(state.env)
|
|
265
314
|
.filter(([k]) => state.selectedKeys.includes(k))
|
|
266
315
|
.sort(([a], [b]) => a.localeCompare(b)), mask: true, ...(state.filePath ? { fromFiles: [state.filePath] } : {}), toFiles: [
|
|
267
316
|
state.destination === 'global'
|