@lkangd/cc-env 1.1.1 → 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 (78) hide show
  1. package/LICENSE +15 -0
  2. package/dist/cli.js +68 -6
  3. package/dist/commands/completion.js +60 -0
  4. package/dist/commands/doctor.js +73 -0
  5. package/dist/commands/preset/edit.js +16 -11
  6. package/dist/commands/preset/rename.js +16 -0
  7. package/dist/commands/run.js +9 -1
  8. package/dist/ink/preset-edit-app.js +112 -0
  9. package/package.json +11 -2
  10. package/.claude/settings.json +0 -6
  11. package/.claude/settings.local.json +0 -8
  12. package/.nvmrc +0 -1
  13. package/CHANGELOG.md +0 -71
  14. package/docs/product-specs/index.draft.md +0 -106
  15. package/docs/product-specs/index.md +0 -911
  16. package/docs/product-specs/optional.md +0 -42
  17. package/docs/references/claude-code-env.md +0 -224
  18. package/docs/superpowers/plans/2026-04-24-cc-env-init-shell-migration.md +0 -1331
  19. package/docs/superpowers/plans/2026-04-24-cc-env.md +0 -1666
  20. package/docs/superpowers/plans/2026-04-26-preset-create-interactive-refactor.md +0 -1432
  21. package/docs/superpowers/specs/2026-04-24-cc-env-design.md +0 -438
  22. package/docs/superpowers/specs/2026-04-24-cc-env-init-shell-migration-design.md +0 -181
  23. package/docs/superpowers/specs/2026-04-26-preset-create-interactive-refactor-design.md +0 -78
  24. package/src/cli.ts +0 -340
  25. package/src/commands/init.ts +0 -139
  26. package/src/commands/preset/create.ts +0 -96
  27. package/src/commands/preset/delete.ts +0 -62
  28. package/src/commands/preset/show.ts +0 -51
  29. package/src/commands/restore.ts +0 -150
  30. package/src/commands/run.ts +0 -158
  31. package/src/core/errors.ts +0 -13
  32. package/src/core/find-claude.ts +0 -70
  33. package/src/core/format.ts +0 -29
  34. package/src/core/fs.ts +0 -18
  35. package/src/core/gitignore.ts +0 -26
  36. package/src/core/logger.ts +0 -11
  37. package/src/core/mask.ts +0 -17
  38. package/src/core/paths.ts +0 -41
  39. package/src/core/process-env.ts +0 -11
  40. package/src/core/schema.ts +0 -55
  41. package/src/core/spawn.ts +0 -36
  42. package/src/flows/init-flow.ts +0 -61
  43. package/src/flows/preset-create-flow.ts +0 -129
  44. package/src/flows/restore-flow.ts +0 -144
  45. package/src/ink/init-app.tsx +0 -110
  46. package/src/ink/preset-create-app.tsx +0 -451
  47. package/src/ink/preset-delete-app.tsx +0 -114
  48. package/src/ink/preset-show-app.tsx +0 -76
  49. package/src/ink/restore-app.tsx +0 -230
  50. package/src/ink/run-preset-select-app.tsx +0 -83
  51. package/src/ink/summary.tsx +0 -91
  52. package/src/services/claude-settings-env-service.ts +0 -72
  53. package/src/services/history-service.ts +0 -48
  54. package/src/services/preset-service.ts +0 -72
  55. package/src/services/project-env-service.ts +0 -128
  56. package/src/services/project-state-service.ts +0 -31
  57. package/src/services/settings-env-service.ts +0 -40
  58. package/src/services/shell-env-service.ts +0 -112
  59. package/src/types.d.ts +0 -19
  60. package/tests/cli/help.test.ts +0 -133
  61. package/tests/cli/init.test.ts +0 -76
  62. package/tests/cli/restore.test.ts +0 -172
  63. package/tests/commands/create.test.ts +0 -263
  64. package/tests/commands/output.test.ts +0 -119
  65. package/tests/commands/run.test.ts +0 -218
  66. package/tests/core/gitignore.test.ts +0 -98
  67. package/tests/core/paths.test.ts +0 -24
  68. package/tests/core/schema-mask.test.ts +0 -182
  69. package/tests/core/spawn.test.ts +0 -47
  70. package/tests/flows/init-flow.test.ts +0 -40
  71. package/tests/flows/preset-create-flow.test.ts +0 -225
  72. package/tests/flows/restore-flow.test.ts +0 -157
  73. package/tests/integration/init-restore.test.ts +0 -406
  74. package/tests/services/claude-shell.test.ts +0 -183
  75. package/tests/services/storage.test.ts +0 -143
  76. package/tsconfig.build.json +0 -9
  77. package/tsconfig.json +0 -22
  78. package/vitest.config.ts +0 -8
@@ -1,451 +0,0 @@
1
- import React, { useState } from 'react'
2
- import { Box, Text, useApp, useInput } from 'ink'
3
-
4
- import {
5
- advancePresetCreateFlow,
6
- createPresetCreateFlowState,
7
- type PresetCreateDestination,
8
- type PresetCreateFlowResult,
9
- type PresetCreateSource,
10
- } from '../flows/preset-create-flow.js'
11
- import type { EnvMap } from '../core/schema.js'
12
- import { EnvSummary } from './summary.js'
13
-
14
- export type PresetCreateAppResult = PresetCreateFlowResult & {
15
- destination: PresetCreateDestination
16
- }
17
-
18
- type PresetCreateAppProps = {
19
- onSubmit: (result: PresetCreateAppResult) => Promise<void> | void
20
- readFile: (filePath: string) => Promise<{ allKeys: string[]; env: EnvMap }>
21
- globalPresetPath: (name: string) => string
22
- projectEnvPath: string
23
- }
24
-
25
- function SourceStep({ cursor }: { cursor: number }) {
26
- const options: { label: string; value: PresetCreateSource }[] = [
27
- { label: 'File import', value: 'file' },
28
- { label: 'Manual input', value: 'manual' },
29
- ]
30
- return (
31
- <Box flexDirection="column">
32
- <Text bold>Select env source</Text>
33
- <Text dimColor>↑/k ↓/j navigate · enter confirm</Text>
34
- <Box flexDirection="column" marginTop={1}>
35
- {options.map((opt, i) => (
36
- <Box key={opt.value}>
37
- <Text>{i === cursor ? '❯ ' : ' '}</Text>
38
- <Text {...(i === cursor ? { color: 'cyan' } : {})}>{opt.label}</Text>
39
- </Box>
40
- ))}
41
- </Box>
42
- </Box>
43
- )
44
- }
45
-
46
- function FilePathStep({ value, error }: { value: string; error?: string }) {
47
- return (
48
- <Box flexDirection="column">
49
- <Text bold>Enter file path (.yaml/.yml/.json)</Text>
50
- <Box marginTop={1}>
51
- <Text dimColor>{'>'} </Text>
52
- <Text color="cyan">{value}</Text>
53
- <Text dimColor>█</Text>
54
- </Box>
55
- {error ? <Text color="red">{error}</Text> : null}
56
- </Box>
57
- )
58
- }
59
-
60
- function KeysStep({
61
- keys,
62
- selectedKeys,
63
- cursor,
64
- }: {
65
- keys: string[]
66
- selectedKeys: string[]
67
- cursor: number
68
- }) {
69
- return (
70
- <Box flexDirection="column">
71
- <Text bold>Select env keys to import</Text>
72
- <Text dimColor>↑/k ↓/j navigate · space toggle · enter confirm</Text>
73
- <Box flexDirection="column" marginTop={1}>
74
- {keys.map((key, i) => {
75
- const isSelected = selectedKeys.includes(key)
76
- return (
77
- <Box key={key}>
78
- <Text>{i === cursor ? '❯ ' : ' '}</Text>
79
- <Text color={isSelected ? 'green' : ''}>{isSelected ? '[x]' : '[ ]'}</Text>
80
- <Text> {key}</Text>
81
- </Box>
82
- )
83
- })}
84
- </Box>
85
- <Box marginTop={1}>
86
- <Text dimColor>{selectedKeys.length} of {keys.length} selected</Text>
87
- </Box>
88
- </Box>
89
- )
90
- }
91
-
92
- function ManualInputStep({
93
- entries,
94
- value,
95
- error,
96
- }: {
97
- entries: [string, string][]
98
- value: string
99
- error?: string
100
- }) {
101
- return (
102
- <Box flexDirection="column">
103
- <Text bold>Enter KEY=VALUE pairs (press q when done)</Text>
104
- {entries.length > 0 ? (
105
- <Box flexDirection="column" marginBottom={1}>
106
- {entries.map(([key, val]) => (
107
- <Box key={key}>
108
- <Text color="yellow">• </Text>
109
- <Text color="magenta">{key}</Text>
110
- <Text dimColor>=</Text>
111
- <Text>{val}</Text>
112
- </Box>
113
- ))}
114
- </Box>
115
- ) : null}
116
- <Box>
117
- <Text dimColor>{'>'} </Text>
118
- <Text color="cyan">{value}</Text>
119
- <Text dimColor>█</Text>
120
- </Box>
121
- {error ? <Text color="red">{error}</Text> : null}
122
- </Box>
123
- )
124
- }
125
-
126
- function NameStep({ value }: { value: string }) {
127
- return (
128
- <Box flexDirection="column">
129
- <Text bold>Enter preset name</Text>
130
- <Box marginTop={1}>
131
- <Text dimColor>{'>'} </Text>
132
- <Text color="cyan">{value}</Text>
133
- <Text dimColor>█</Text>
134
- </Box>
135
- </Box>
136
- )
137
- }
138
-
139
- function DestinationStep({ cursor }: { cursor: number }) {
140
- const options: { label: string; value: PresetCreateDestination }[] = [
141
- { label: 'Global preset', value: 'global' },
142
- { label: 'Project preset', value: 'project' },
143
- ]
144
- return (
145
- <Box flexDirection="column">
146
- <Text bold>Select save destination</Text>
147
- <Text dimColor>↑/k ↓/j navigate · enter confirm</Text>
148
- <Box flexDirection="column" marginTop={1}>
149
- {options.map((opt, i) => (
150
- <Box key={opt.value}>
151
- <Text>{i === cursor ? '❯ ' : ' '}</Text>
152
- <Text {...(i === cursor ? { color: 'cyan' } : {})}>{opt.label}</Text>
153
- </Box>
154
- ))}
155
- </Box>
156
- </Box>
157
- )
158
- }
159
-
160
- export function PresetCreateApp({
161
- onSubmit,
162
- readFile,
163
- globalPresetPath,
164
- projectEnvPath,
165
- }: PresetCreateAppProps) {
166
- const { exit } = useApp()
167
- const [state, setState] = useState(createPresetCreateFlowState)
168
- const [textInput, setTextInput] = useState('')
169
- const [listCursor, setListCursor] = useState(0)
170
- const [allKeys, setAllKeys] = useState<string[]>([])
171
- const [fileEnv, setFileEnv] = useState<EnvMap>({})
172
-
173
- useInput((input, key) => {
174
- if (key.escape) {
175
- exit()
176
- return
177
- }
178
-
179
- if (state.step === 'source') {
180
- if (input === 'q') {
181
- exit()
182
- return
183
- }
184
- if (key.upArrow || input === 'k') {
185
- setListCursor((c) => Math.max(0, c - 1))
186
- return
187
- }
188
- if (key.downArrow || input === 'j') {
189
- setListCursor((c) => Math.min(1, c + 1))
190
- return
191
- }
192
- if (key.return) {
193
- const source: PresetCreateSource = listCursor === 0 ? 'file' : 'manual'
194
- setState((s) => advancePresetCreateFlow(s, { type: 'select-source', source }))
195
- setListCursor(0)
196
- setTextInput('')
197
- return
198
- }
199
- }
200
-
201
- if (state.step === 'filePath') {
202
- if (input === 'q') {
203
- exit()
204
- return
205
- }
206
- if (key.backspace || key.delete) {
207
- setTextInput((v) => v.slice(0, -1))
208
- return
209
- }
210
- if (key.return) {
211
- void (async () => {
212
- try {
213
- const result = await readFile(textInput)
214
- if (result.allKeys.length === 0) {
215
- setState((s) => advancePresetCreateFlow(s, {
216
- type: 'set-error',
217
- error: 'No valid env keys found in file',
218
- }))
219
- return
220
- }
221
- setAllKeys(result.allKeys)
222
- setFileEnv(result.env)
223
- setState((s) => advancePresetCreateFlow(s, {
224
- type: 'set-file-path',
225
- filePath: textInput,
226
- }))
227
- setListCursor(0)
228
- } catch (err) {
229
- const message = err instanceof Error ? err.message : 'Failed to read file'
230
- setState((s) => advancePresetCreateFlow(s, {
231
- type: 'set-error',
232
- error: message,
233
- }))
234
- }
235
- })()
236
- return
237
- }
238
- if (input && !key.ctrl && !key.meta) {
239
- setTextInput((v) => v + input)
240
- return
241
- }
242
- }
243
-
244
- if (state.step === 'keys') {
245
- if (input === 'q') {
246
- exit()
247
- return
248
- }
249
- if (key.upArrow || input === 'k') {
250
- setListCursor((c) => Math.max(0, c - 1))
251
- return
252
- }
253
- if (key.downArrow || input === 'j') {
254
- setListCursor((c) => Math.min(allKeys.length - 1, c + 1))
255
- return
256
- }
257
- if (input === ' ') {
258
- const targetKey = allKeys[listCursor]
259
- if (targetKey) {
260
- const newSelected = state.selectedKeys.includes(targetKey)
261
- ? state.selectedKeys.filter((k) => k !== targetKey)
262
- : [...state.selectedKeys, targetKey]
263
- setState((s) => ({ ...s, selectedKeys: newSelected }))
264
- }
265
- return
266
- }
267
- if (key.return && state.selectedKeys.length > 0) {
268
- const selectedEnv: EnvMap = {}
269
- for (const k of state.selectedKeys) {
270
- selectedEnv[k] = fileEnv[k] ?? ''
271
- }
272
- setState((s) => advancePresetCreateFlow(s, {
273
- type: 'select-keys',
274
- keys: state.selectedKeys,
275
- env: selectedEnv,
276
- }))
277
- setTextInput('')
278
- return
279
- }
280
- }
281
-
282
- if (state.step === 'manualInput') {
283
- if (input === 'q' && textInput === '') {
284
- if (state.selectedKeys.length === 0) {
285
- setState((s) => advancePresetCreateFlow(s, {
286
- type: 'set-error',
287
- error: 'Add at least one KEY=VALUE pair',
288
- }))
289
- return
290
- }
291
- setState((s) => advancePresetCreateFlow(s, { type: 'finish-manual-input' }))
292
- setTextInput('')
293
- return
294
- }
295
- if (key.backspace || key.delete) {
296
- setTextInput((v) => v.slice(0, -1))
297
- return
298
- }
299
- if (key.return) {
300
- const separatorIndex = textInput.indexOf('=')
301
- if (separatorIndex <= 0) {
302
- setState((s) => advancePresetCreateFlow(s, {
303
- type: 'set-error',
304
- error: 'Format must be KEY=VALUE',
305
- }))
306
- return
307
- }
308
- const k = textInput.slice(0, separatorIndex)
309
- const v = textInput.slice(separatorIndex + 1)
310
- if (!/^[A-Z0-9_]+$/.test(k)) {
311
- setState((s) => advancePresetCreateFlow(s, {
312
- type: 'set-error',
313
- error: 'Key must match [A-Z0-9_]+',
314
- }))
315
- return
316
- }
317
- setState((s) => advancePresetCreateFlow(s, {
318
- type: 'add-manual-pair',
319
- key: k,
320
- value: v,
321
- }))
322
- setTextInput('')
323
- return
324
- }
325
- if (input && !key.ctrl && !key.meta) {
326
- setTextInput((v) => v + input)
327
- return
328
- }
329
- }
330
-
331
- if (state.step === 'name') {
332
- if (input === 'q') {
333
- exit()
334
- return
335
- }
336
- if (key.backspace || key.delete) {
337
- setTextInput((v) => v.slice(0, -1))
338
- return
339
- }
340
- if (key.return && textInput.trim().length > 0) {
341
- setState((s) => advancePresetCreateFlow(s, {
342
- type: 'set-name',
343
- name: textInput.trim(),
344
- }))
345
- setListCursor(0)
346
- return
347
- }
348
- if (input && !key.ctrl && !key.meta) {
349
- setTextInput((v) => v + input)
350
- return
351
- }
352
- }
353
-
354
- if (state.step === 'destination') {
355
- if (input === 'q') {
356
- exit()
357
- return
358
- }
359
- if (key.upArrow || input === 'k') {
360
- setListCursor((c) => Math.max(0, c - 1))
361
- return
362
- }
363
- if (key.downArrow || input === 'j') {
364
- setListCursor((c) => Math.min(1, c + 1))
365
- return
366
- }
367
- if (key.return) {
368
- const destination: PresetCreateDestination = listCursor === 0 ? 'global' : 'project'
369
- setState((s) => advancePresetCreateFlow(s, {
370
- type: 'select-destination',
371
- destination,
372
- }))
373
- return
374
- }
375
- }
376
-
377
- if (state.step === 'confirm') {
378
- if (input === 'q') {
379
- exit()
380
- return
381
- }
382
- if (key.return && state.destination && state.presetName) {
383
- const doneState = advancePresetCreateFlow(state, { type: 'confirm' })
384
- setState(doneState)
385
- void Promise.resolve(
386
- onSubmit({
387
- source: state.source!,
388
- filePath: state.filePath,
389
- env: state.env,
390
- selectedKeys: state.selectedKeys,
391
- presetName: state.presetName,
392
- destination: state.destination,
393
- }),
394
- ).finally(() => {
395
- exit()
396
- })
397
- }
398
- }
399
- })
400
-
401
- if (state.step === 'done') {
402
- return (
403
- <Box flexDirection="column">
404
- <Text color="green">Done</Text>
405
- </Box>
406
- )
407
- }
408
-
409
- return (
410
- <Box flexDirection="column">
411
- {state.step === 'source' && <SourceStep cursor={listCursor} />}
412
- {state.step === 'filePath' && (
413
- <FilePathStep value={textInput} {...(state.error ? { error: state.error } : {})} />
414
- )}
415
- {state.step === 'keys' && (
416
- <KeysStep keys={allKeys} selectedKeys={state.selectedKeys} cursor={listCursor} />
417
- )}
418
- {state.step === 'manualInput' && (
419
- <ManualInputStep
420
- entries={state.selectedKeys.map((k) => [k, state.env[k] ?? ''] as [string, string])}
421
- value={textInput}
422
- {...(state.error ? { error: state.error } : {})}
423
- />
424
- )}
425
- {state.step === 'name' && <NameStep value={textInput} />}
426
- {state.step === 'destination' && <DestinationStep cursor={listCursor} />}
427
- {state.step === 'confirm' && state.destination ? (
428
- <Box flexDirection="column">
429
- <EnvSummary
430
- title={`Preset: ${state.presetName}`}
431
- entries={
432
- Object.entries(state.env)
433
- .filter(([k]) => state.selectedKeys.includes(k))
434
- .sort(([a], [b]) => a.localeCompare(b)) as [string, string][]
435
- }
436
- mask
437
- {...(state.filePath ? { fromFiles: [state.filePath] } : {})}
438
- toFiles={[
439
- state.destination === 'global'
440
- ? globalPresetPath(state.presetName)
441
- : projectEnvPath,
442
- ]}
443
- />
444
- <Box marginTop={1}>
445
- <Text dimColor>Press enter to confirm · q to cancel</Text>
446
- </Box>
447
- </Box>
448
- ) : null}
449
- </Box>
450
- )
451
- }
@@ -1,114 +0,0 @@
1
- import React, { useMemo, useState } from 'react'
2
- import { Box, Text, useApp, useInput } from 'ink'
3
-
4
- import type { EnvMap } from '../core/schema.js'
5
- import { EnvEntries } from './summary.js'
6
-
7
- type PresetSource = 'global' | 'project'
8
-
9
- export type PresetDeleteItem = {
10
- name: string
11
- env: EnvMap
12
- source: PresetSource
13
- }
14
-
15
- type DeleteStep = 'browsing' | 'confirming'
16
-
17
- export function PresetDeleteApp({
18
- presets,
19
- onSubmit,
20
- }: {
21
- presets: Array<PresetDeleteItem>
22
- onSubmit: (preset: PresetDeleteItem) => void
23
- }) {
24
- const { exit } = useApp()
25
- const [cursor, setCursor] = useState(0)
26
- const [step, setStep] = useState<DeleteStep>('browsing')
27
-
28
- const activePreset = presets[cursor]
29
-
30
- const entries = useMemo(
31
- () =>
32
- activePreset
33
- ? (Object.entries(activePreset.env).sort(([a], [b]) => a.localeCompare(b)) as [string, string][])
34
- : [],
35
- [activePreset],
36
- )
37
-
38
- useInput((input, key) => {
39
- if (step === 'browsing') {
40
- if (key.escape || input.toLowerCase() === 'q') {
41
- exit()
42
- return
43
- }
44
-
45
- if (key.upArrow || input === 'k') {
46
- setCursor((c) => Math.max(0, c - 1))
47
- return
48
- }
49
-
50
- if (key.downArrow || input === 'j') {
51
- setCursor((c) => Math.min(presets.length - 1, c + 1))
52
- return
53
- }
54
-
55
- if (key.return) {
56
- setStep('confirming')
57
- return
58
- }
59
- }
60
-
61
- if (step === 'confirming') {
62
- if (input.toLowerCase() === 'y') {
63
- onSubmit(activePreset!)
64
- exit()
65
- return
66
- }
67
-
68
- if (input.toLowerCase() === 'n' || key.escape) {
69
- setStep('browsing')
70
- return
71
- }
72
- }
73
- })
74
-
75
- return (
76
- <Box flexDirection="column">
77
- <Text>Preset delete</Text>
78
- <Text dimColor>
79
- {step === 'browsing'
80
- ? '↑/k ↓/j navigate · Enter select · q exit'
81
- : 'y confirm · n cancel'}
82
- </Text>
83
- <Box marginTop={1}>
84
- <Box flexDirection="column" width={28} marginRight={2}>
85
- <Text bold color="cyan">Presets</Text>
86
- <Box flexDirection="column" marginTop={1}>
87
- {presets.map((preset, index) => (
88
- <Box key={`${preset.source}:${preset.name}`}>
89
- <Text>{index === cursor ? '❯ ' : ' '}</Text>
90
- <Text {...(preset.source === 'project' ? { color: 'yellow' } : {})}>{preset.name}</Text>
91
- <Text dimColor> ({preset.source})</Text>
92
- </Box>
93
- ))}
94
- </Box>
95
- </Box>
96
- <Box flexDirection="column" flexGrow={1} borderStyle="round" borderColor="red" paddingX={1}>
97
- <Text bold color="red">{activePreset!.name}</Text>
98
- <Text dimColor>{activePreset!.source === 'project' ? 'Project preset' : 'Global preset'}</Text>
99
- <Box flexDirection="column" marginTop={1}>
100
- <EnvEntries entries={entries} />
101
- </Box>
102
- </Box>
103
- </Box>
104
- {step === 'confirming' && (
105
- <Box marginTop={1}>
106
- <Text color="red">Delete preset </Text>
107
- <Text bold>{activePreset!.name}</Text>
108
- <Text color="red">?</Text>
109
- <Text dimColor> y/n</Text>
110
- </Box>
111
- )}
112
- </Box>
113
- )
114
- }
@@ -1,76 +0,0 @@
1
- import React, { useMemo, useState } from 'react'
2
- import { Box, Text, useApp, useInput } from 'ink'
3
-
4
- import type { EnvMap } from '../core/schema.js'
5
- import { EnvEntries } from './summary.js'
6
-
7
- export type PresetSource = 'global' | 'project'
8
-
9
- export type PresetShowItem = {
10
- name: string
11
- env: EnvMap
12
- source: PresetSource
13
- }
14
-
15
- export function PresetShowApp({
16
- presets,
17
- }: {
18
- presets: Array<PresetShowItem>
19
- }) {
20
- const { exit } = useApp()
21
- const [cursor, setCursor] = useState(0)
22
- const activePreset = presets[cursor]
23
-
24
- const entries = useMemo(
25
- () =>
26
- activePreset
27
- ? (Object.entries(activePreset.env).sort(([a], [b]) => a.localeCompare(b)) as [string, string][])
28
- : [],
29
- [activePreset],
30
- )
31
-
32
- useInput((input, key) => {
33
- if (key.escape || input.toLowerCase() === 'q') {
34
- exit()
35
- return
36
- }
37
-
38
- if (key.upArrow || input === 'k') {
39
- setCursor((c) => Math.max(0, c - 1))
40
- return
41
- }
42
-
43
- if (key.downArrow || input === 'j') {
44
- setCursor((c) => Math.min(presets.length - 1, c + 1))
45
- return
46
- }
47
- })
48
-
49
- return (
50
- <Box flexDirection="column">
51
- <Text>Preset show</Text>
52
- <Text dimColor>↑/k ↓/j navigate · q exit</Text>
53
- <Box marginTop={1}>
54
- <Box flexDirection="column" width={28} marginRight={2}>
55
- <Text bold color="cyan">Presets</Text>
56
- <Box flexDirection="column" marginTop={1}>
57
- {presets.map((preset, index) => (
58
- <Box key={`${preset.source}:${preset.name}`}>
59
- <Text>{index === cursor ? '❯ ' : ' '}</Text>
60
- <Text {...(preset.source === 'project' ? { color: 'yellow' } : {})}>{preset.name}</Text>
61
- <Text dimColor> ({preset.source})</Text>
62
- </Box>
63
- ))}
64
- </Box>
65
- </Box>
66
- <Box flexDirection="column" flexGrow={1} borderStyle="round" borderColor="green" paddingX={1}>
67
- <Text bold color="green">{activePreset?.name ?? 'Preview'}</Text>
68
- <Text dimColor>{activePreset?.source === 'project' ? 'Project preset' : 'Global preset'}</Text>
69
- <Box flexDirection="column" marginTop={1}>
70
- <EnvEntries entries={entries} />
71
- </Box>
72
- </Box>
73
- </Box>
74
- </Box>
75
- )
76
- }