sitevision-cli 1.0.0-beta.3 → 1.0.0-beta.30

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 (72) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/app.d.ts +1 -1
  3. package/dist/app.js +59 -8
  4. package/dist/cli.js +125 -40
  5. package/dist/commands/build.js +1 -1
  6. package/dist/commands/deploy.d.ts +2 -2
  7. package/dist/commands/deploy.js +137 -28
  8. package/dist/commands/dev.d.ts +8 -10
  9. package/dist/commands/dev.js +70 -371
  10. package/dist/commands/watch.js +5 -23
  11. package/dist/components/AnimatedLogo.js +8 -2
  12. package/dist/components/AuthLoginScreen.d.ts +21 -0
  13. package/dist/components/AuthLoginScreen.js +90 -0
  14. package/dist/components/DevPropertiesForm.d.ts +2 -1
  15. package/dist/components/DevPropertiesForm.js +198 -33
  16. package/dist/components/MainMenu.js +5 -0
  17. package/dist/components/PasswordInput.js +2 -1
  18. package/dist/components/SetupFlow.d.ts +2 -1
  19. package/dist/components/SetupFlow.js +99 -10
  20. package/dist/shell/AddonPicker.d.ts +14 -0
  21. package/dist/shell/AddonPicker.js +54 -0
  22. package/dist/shell/Changelog.d.ts +10 -0
  23. package/dist/shell/Changelog.js +44 -0
  24. package/dist/shell/CommandPalette.d.ts +12 -0
  25. package/dist/shell/CommandPalette.js +63 -0
  26. package/dist/shell/ConfigForm.d.ts +36 -0
  27. package/dist/shell/ConfigForm.js +558 -0
  28. package/dist/shell/Frame.d.ts +59 -0
  29. package/dist/shell/Frame.js +135 -0
  30. package/dist/shell/Help.d.ts +6 -0
  31. package/dist/shell/Help.js +31 -0
  32. package/dist/shell/Popover.d.ts +12 -0
  33. package/dist/shell/Popover.js +15 -0
  34. package/dist/shell/Settings.d.ts +6 -0
  35. package/dist/shell/Settings.js +96 -0
  36. package/dist/shell/Shell.d.ts +10 -0
  37. package/dist/shell/Shell.js +620 -0
  38. package/dist/shell/Tabs.d.ts +36 -0
  39. package/dist/shell/Tabs.js +90 -0
  40. package/dist/shell/actions.d.ts +48 -0
  41. package/dist/shell/actions.js +0 -0
  42. package/dist/types/index.d.ts +37 -3
  43. package/dist/utils/config.d.ts +10 -0
  44. package/dist/utils/config.js +14 -0
  45. package/dist/utils/environments.d.ts +20 -0
  46. package/dist/utils/environments.js +74 -0
  47. package/dist/utils/i18n.d.ts +12 -0
  48. package/dist/utils/i18n.js +300 -0
  49. package/dist/utils/jsonc.d.ts +19 -0
  50. package/dist/utils/jsonc.js +74 -0
  51. package/dist/utils/keychain.d.ts +9 -0
  52. package/dist/utils/keychain.js +54 -0
  53. package/dist/utils/oauth2-auth.d.ts +64 -0
  54. package/dist/utils/oauth2-auth.js +242 -0
  55. package/dist/utils/password-prompt.d.ts +5 -0
  56. package/dist/utils/password-prompt.js +28 -0
  57. package/dist/utils/process-runner.d.ts +13 -4
  58. package/dist/utils/process-runner.js +63 -37
  59. package/dist/utils/project-detection.d.ts +90 -4
  60. package/dist/utils/project-detection.js +389 -63
  61. package/dist/utils/session-cookie-auth.d.ts +35 -0
  62. package/dist/utils/session-cookie-auth.js +99 -0
  63. package/dist/utils/sitevision-api.d.ts +72 -5
  64. package/dist/utils/sitevision-api.js +220 -38
  65. package/dist/utils/sitevision-scripts-runner.d.ts +2 -1
  66. package/dist/utils/sitevision-scripts-runner.js +18 -7
  67. package/dist/utils/tasks.d.ts +50 -0
  68. package/dist/utils/tasks.js +439 -0
  69. package/dist/utils/workspace.d.ts +17 -0
  70. package/dist/utils/workspace.js +67 -0
  71. package/package.json +5 -2
  72. package/readme.md +103 -121
@@ -0,0 +1,59 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { ProjectInfo } from '../types/index.js';
3
+ import { type Task } from '../utils/tasks.js';
4
+ export declare const ACCENT = "cyan";
5
+ export declare const NARROW_BELOW = 100;
6
+ /** Navigator width for a terminal: a quarter of the columns, within 32..48. */
7
+ export declare function navWidth(columns: number): number;
8
+ export interface TopBarProps {
9
+ context: string;
10
+ domain?: string;
11
+ auth: {
12
+ ready: boolean;
13
+ label: string;
14
+ };
15
+ version: string;
16
+ environment?: {
17
+ name: string;
18
+ color: 'green' | 'yellow' | 'red';
19
+ };
20
+ }
21
+ export declare function TopBar({ context, domain, auth, version, environment, }: TopBarProps): import("react").JSX.Element;
22
+ /** Three-letter type marker; unknown manifest types show as "???". */
23
+ export declare function typeGlyph(manifest: ProjectInfo['manifest']): string;
24
+ export declare function appStatus(project: ProjectInfo): {
25
+ deps: boolean;
26
+ config: boolean;
27
+ sync: number;
28
+ signing: boolean;
29
+ scriptsWarning: string | undefined;
30
+ };
31
+ /** The name shown for an app in the navigator, and what the filter matches. */
32
+ export declare function appLabel(app: ProjectInfo): string;
33
+ /** Indices into `apps` whose label fuzzy-matches the filter. */
34
+ export declare function navMatches(apps: ProjectInfo[], filter: string): number[];
35
+ /** Next selectable index when moving by `delta`, wrapping at both ends. */
36
+ export declare function navMove(ring: number[], selected: number, delta: number): number;
37
+ export interface NavigatorProps {
38
+ apps: ProjectInfo[];
39
+ groupOf: (app: ProjectInfo) => string;
40
+ selected: number;
41
+ focused: boolean;
42
+ tasks: Task[];
43
+ height: number;
44
+ single: boolean;
45
+ settingsSelected?: boolean;
46
+ width: number;
47
+ filter?: string;
48
+ }
49
+ export declare function Navigator({ apps, groupOf, selected, focused, tasks, height, single, settingsSelected, width, filter, }: NavigatorProps): import("react").JSX.Element;
50
+ export declare function NavigatorStrip({ apps, selected, focused, width, filter, }: Pick<NavigatorProps, 'apps' | 'selected' | 'focused' | 'width' | 'filter'>): import("react").JSX.Element;
51
+ export declare function elapsed(task: Task): string;
52
+ export interface Hint {
53
+ key: string;
54
+ label: string;
55
+ }
56
+ export declare function BottomBar({ hints, right }: {
57
+ hints: Hint[];
58
+ right: ReactNode;
59
+ }): import("react").JSX.Element;
@@ -0,0 +1,135 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import Spinner from 'ink-spinner';
4
+ import { appTypeOf, getPackageJsonSyncChanges, localizedText, } from '../utils/project-detection.js';
5
+ import { fuzzyMatch } from './actions.js';
6
+ import { t } from '../utils/i18n.js';
7
+ export const ACCENT = 'cyan';
8
+ export const NARROW_BELOW = 100;
9
+ /** Navigator width for a terminal: a quarter of the columns, within 32..48. */
10
+ export function navWidth(columns) {
11
+ return Math.min(48, Math.max(32, Math.floor(columns / 4)));
12
+ }
13
+ export function TopBar({ context, domain, auth, version, environment, }) {
14
+ return (_jsxs(Box, { paddingX: 1, justifyContent: "space-between", height: 1, children: [_jsx(Box, { flexShrink: 1, marginRight: 2, children: _jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { bold: true, color: ACCENT, children: "svc" }), environment && (_jsxs(Text, { children: [' ', _jsx(Text, { backgroundColor: environment.color, color: "black", bold: true, children: ` ${environment.name.toUpperCase()} ` })] })), _jsxs(Text, { dimColor: true, children: [" ", context] })] }) }), _jsx(Box, { flexShrink: 0, children: _jsxs(Text, { wrap: "truncate", children: [domain && _jsxs(Text, { dimColor: true, children: [domain, " "] }), _jsx(Text, { color: auth.ready ? 'green' : 'yellow', children: auth.ready ? '●' : '○' }), _jsxs(Text, { children: [" ", auth.label, " "] }), _jsxs(Text, { dimColor: true, children: ["v", version] })] }) })] }));
15
+ }
16
+ const TYPE_GLYPH = {
17
+ web: 'web',
18
+ widget: 'wgt',
19
+ rest: 'rst',
20
+ mcp: 'mcp',
21
+ };
22
+ /** Three-letter type marker; unknown manifest types show as "???". */
23
+ export function typeGlyph(manifest) {
24
+ const type = appTypeOf(manifest);
25
+ return type ? TYPE_GLYPH[type] : '???';
26
+ }
27
+ export function appStatus(project) {
28
+ const sync = getPackageJsonSyncChanges(project.root).length;
29
+ return {
30
+ deps: project.hasNodeModules,
31
+ config: Boolean(project.devProperties),
32
+ sync,
33
+ signing: project.hasSigningProperties,
34
+ scriptsWarning: undefined,
35
+ };
36
+ }
37
+ function Dots({ project }) {
38
+ const s = appStatus(project);
39
+ const dot = (ok, warn = false) => (_jsx(Text, { color: ok ? 'green' : warn ? 'yellow' : 'red', children: "\u25CF" }));
40
+ return (_jsxs(Text, { children: [dot(s.deps), dot(s.config), dot(s.sync === 0, s.sync > 0), dot(s.signing, !s.signing)] }));
41
+ }
42
+ /** The name shown for an app in the navigator, and what the filter matches. */
43
+ export function appLabel(app) {
44
+ return localizedText(app.manifest.name) || app.manifest.id;
45
+ }
46
+ /** Indices into `apps` whose label fuzzy-matches the filter. */
47
+ export function navMatches(apps, filter) {
48
+ return apps
49
+ .map((_, index) => index)
50
+ .filter(index => fuzzyMatch(filter, appLabel(apps[index])));
51
+ }
52
+ /** Next selectable index when moving by `delta`, wrapping at both ends. */
53
+ export function navMove(ring, selected, delta) {
54
+ if (ring.length === 0)
55
+ return selected;
56
+ const at = ring.indexOf(selected);
57
+ return ring[at === -1 ? 0 : (at + delta + ring.length) % ring.length];
58
+ }
59
+ export function Navigator({ apps, groupOf, selected, focused, tasks, height, single, settingsSelected = false, width, filter = '', }) {
60
+ // Row: marker(1) glyph(3) sp name sp version(6) sp dots(4) inside padding + border.
61
+ const rowWidth = width - 3;
62
+ const nameWidth = rowWidth - 17;
63
+ const running = tasks.filter(task => task.status === 'running');
64
+ // Every row is exactly one line; nothing may shrink or the rows overlap.
65
+ const rows = [];
66
+ const rowApp = [];
67
+ let lastGroup;
68
+ for (const [index, app] of apps.entries()) {
69
+ const group = groupOf(app);
70
+ if (!single && group !== lastGroup) {
71
+ rows.push(_jsx(Box, { height: 1, flexShrink: 0, children: _jsxs(Text, { dimColor: true, wrap: "truncate", children: [' ', group] }) }, `g-${index}-${group}`));
72
+ rowApp.push(-1);
73
+ lastGroup = group;
74
+ }
75
+ rowApp.push(index);
76
+ const active = index === selected;
77
+ const busy = running.some(task => task.appRoot === app.root);
78
+ const name = appLabel(app);
79
+ rows.push(_jsxs(Box, { width: rowWidth, height: 1, flexShrink: 0, children: [_jsxs(Text, { backgroundColor: active && focused ? ACCENT : undefined, color: active && focused ? 'black' : undefined, bold: active, wrap: "truncate", children: [active ? '▎' : ' ', _jsx(Text, { dimColor: !active, children: typeGlyph(app.manifest) }), ' ', name.padEnd(nameWidth).slice(0, nameWidth), ' ', _jsx(Text, { dimColor: true, children: app.manifest.version.padStart(6).slice(0, 6) }), ' '] }), busy ? (_jsx(Text, { color: ACCENT, children: _jsx(Spinner, { type: "dots" }) })) : (_jsx(Dots, { project: app }))] }, app.root));
80
+ }
81
+ if (rows.length === 0) {
82
+ rows.push(_jsx(Box, { height: 1, flexShrink: 0, children: _jsx(Text, { dimColor: true, children: ' ' + t('no matches') }) }, "none"));
83
+ rowApp.push(-1);
84
+ }
85
+ // Window the list so the selected app stays visible; the lines outside
86
+ // are summarised as "… n more".
87
+ // Top border, header and legend, then the settings row and running tasks.
88
+ const fixed = 3 + (single ? 0 : 2) + (running.length > 0 ? running.length + 2 : 0);
89
+ const avail = Math.max(3, height - fixed);
90
+ let shown = rows;
91
+ if (rows.length > avail) {
92
+ const target = settingsSelected
93
+ ? rows.length - 1
94
+ : rowApp.indexOf(selected);
95
+ const start = Math.max(0, Math.min(target - Math.floor(avail / 2), rows.length - avail));
96
+ const end = start + avail;
97
+ shown = rows.slice(start, end);
98
+ const more = (n, arrow) => (_jsx(Box, { height: 1, flexShrink: 0, children: _jsxs(Text, { dimColor: true, children: [' ', t('{arrow} {n} more', { arrow, n })] }) }, `more-${arrow}`));
99
+ if (start > 0)
100
+ shown[0] = more(start, '↑');
101
+ if (end < rows.length)
102
+ shown[shown.length - 1] = more(rows.length - end, '↓');
103
+ }
104
+ return (_jsxs(Box, { flexDirection: "column", width: width, height: height, borderStyle: "single", borderColor: focused ? ACCENT : undefined, borderDimColor: !focused, borderBottom: false, borderLeft: false, paddingX: 1, overflow: "hidden", children: [filter ? (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), filter, _jsx(Text, { inverse: true, children: " " }), _jsxs(Text, { dimColor: true, children: [' ', apps.length === 1
105
+ ? t('1 match')
106
+ : t('{n} matches', { n: apps.length })] })] })) : (_jsx(Text, { bold: true, color: focused ? ACCENT : undefined, dimColor: !focused, children: single
107
+ ? t('APP')
108
+ : apps.length === 1
109
+ ? t('WORKSPACE 1 app')
110
+ : t('WORKSPACE {n} apps', { n: apps.length }) })), shown, _jsx(Text, { dimColor: true, children: ' ' + t('deps·config·sync·signing') }), !single && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { backgroundColor: settingsSelected && focused ? ACCENT : undefined, color: settingsSelected && focused ? 'black' : undefined, bold: settingsSelected, children: [settingsSelected ? '▎' : ' ', "\u2699 ", t('Workspace settings')] }) })), running.length > 0 && (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: t('TASKS') }), running.map(task => (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { color: ACCENT, children: _jsx(Spinner, { type: "dots" }) }), ' ', task.label, " ", task.appName, " ", _jsx(Text, { dimColor: true, children: elapsed(task) })] }, task.id)))] }))] }));
111
+ }
112
+ export function NavigatorStrip({ apps, selected, focused, width, filter = '', }) {
113
+ const label = (app) => ` ${typeGlyph(app.manifest)} ${appLabel(app)} `;
114
+ // Scroll the strip so the selection stays on screen: walk left from it
115
+ // until the row is full.
116
+ const budget = width - 2 - (filter ? filter.length + 5 : 0);
117
+ const from = Math.min(Math.max(0, selected), apps.length - 1);
118
+ let start = from;
119
+ let used = 0;
120
+ for (let i = from; i >= 0; i--) {
121
+ used += label(apps[i]).length;
122
+ if (used > budget)
123
+ break;
124
+ start = i;
125
+ }
126
+ return (_jsx(Box, { paddingX: 1, children: _jsxs(Text, { wrap: "truncate", children: [filter && (_jsxs(Text, { children: [_jsx(Text, { color: ACCENT, children: "\u276F " }), filter, _jsx(Text, { dimColor: true, children: " \u00B7" })] })), apps.slice(start).map((app, index) => (_jsx(Text, { backgroundColor: index + start === selected && focused ? ACCENT : undefined, color: index + start === selected && focused ? 'black' : undefined, bold: index + start === selected, children: label(app) }, app.root)))] }) }));
127
+ }
128
+ export function elapsed(task) {
129
+ const ms = (task.endedAt ?? Date.now()) - task.startedAt;
130
+ const s = Math.round(ms / 1000);
131
+ return s < 60 ? `${s}s` : `${Math.floor(s / 60)}m ${s % 60}s`;
132
+ }
133
+ export function BottomBar({ hints, right }) {
134
+ return (_jsxs(Box, { paddingX: 1, justifyContent: "space-between", children: [_jsx(Text, { wrap: "truncate", children: hints.map(h => (_jsxs(Text, { children: [_jsx(Text, { bold: true, color: ACCENT, children: h.key }), _jsxs(Text, { dimColor: true, children: [" ", h.label, " "] })] }, h.key + h.label))) }), right] }));
135
+ }
@@ -0,0 +1,6 @@
1
+ import { type Hint } from './Frame.js';
2
+ export declare function HelpPanel({ here, height, onClose, }: {
3
+ here: Hint[];
4
+ height: number;
5
+ onClose: () => void;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { t } from '../utils/i18n.js';
4
+ import { ACCENT } from './Frame.js';
5
+ import { actions } from './actions.js';
6
+ import { GROUPS } from './CommandPalette.js';
7
+ const MOVE = [
8
+ ['Tab', 'switch pane'],
9
+ ['1–4', 'tabs'],
10
+ ['←→', 'tabs'],
11
+ ['/', 'commands'],
12
+ ['?', 'help'],
13
+ ['Esc', 'back'],
14
+ ];
15
+ function Row({ keys, label }) {
16
+ return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { bold: true, color: ACCENT, children: keys.padEnd(6) }), label] }));
17
+ }
18
+ export function HelpPanel({ here, height, onClose, }) {
19
+ useInput((input, key) => {
20
+ if (key.escape || key.return || input === '?' || input === 'q')
21
+ onClose();
22
+ });
23
+ // Only keys not already listed elsewhere, e.g. f and x on the Log tab.
24
+ const local = here.filter(hint => actions.every(action => action.key !== hint.key) &&
25
+ MOVE.every(([keys]) => keys !== hint.key));
26
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: height, overflow: "hidden", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Keys') }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", t('Esc close')] })] }), _jsxs(Box, { flexWrap: "wrap", columnGap: 4, children: [_jsx(Box, { flexDirection: "column", marginTop: 1, children: GROUPS.map(group => (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: t(group.label) }), actions.flatMap(action => action.group === group.id && action.key
27
+ ? [
28
+ _jsx(Row, { keys: action.key, label: t(action.label) }, action.id),
29
+ ]
30
+ : [])] }, group.id))) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [local.length > 0 && (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { bold: true, dimColor: true, children: t('Here') }), local.map(hint => (_jsx(Row, { keys: hint.key, label: hint.label }, hint.key + hint.label)))] })), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, dimColor: true, children: t('Move around') }), MOVE.map(([keys, label]) => (_jsx(Row, { keys: keys, label: t(label) }, keys + label)))] })] })] })] }));
31
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from 'react';
2
+ /**
3
+ * A bordered box floating centred over the frame. Render it as the last child
4
+ * of the root so it draws on top; Ink paints in tree order.
5
+ */
6
+ export declare function Popover({ columns, rows, width, height, children, }: {
7
+ columns: number;
8
+ rows: number;
9
+ width: number;
10
+ height: number;
11
+ children: ReactNode;
12
+ }): import("react").JSX.Element;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { ACCENT } from './Frame.js';
4
+ /**
5
+ * A bordered box floating centred over the frame. Render it as the last child
6
+ * of the root so it draws on top; Ink paints in tree order.
7
+ */
8
+ export function Popover({ columns, rows, width, height, children, }) {
9
+ const w = Math.max(20, Math.min(width, columns - 4));
10
+ const h = Math.max(6, Math.min(height, rows - 2));
11
+ // Ink only overwrites cells it writes to, so blank the interior first or the
12
+ // content underneath shows through around shorter lines.
13
+ const blank = ' '.repeat(w - 2);
14
+ return (_jsxs(Box, { position: "absolute", top: Math.floor((rows - h) / 2), left: Math.floor((columns - w) / 2), width: w, height: h, flexDirection: "column", borderStyle: "round", borderColor: ACCENT, children: [_jsx(Box, { position: "absolute", flexDirection: "column", children: Array.from({ length: h - 2 }, (_, i) => (_jsx(Text, { children: blank }, i))) }), _jsx(Box, { flexDirection: "column", height: h - 2, overflow: "hidden", children: children })] }));
15
+ }
@@ -0,0 +1,6 @@
1
+ /** Global preferences, saved per field like the Config tab. */
2
+ export declare function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }: {
3
+ onChanged: () => void;
4
+ onClose: () => void;
5
+ onOpenWorkspace?: () => void;
6
+ }): import("react").JSX.Element;
@@ -0,0 +1,96 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { getSettings, setSettings, settingsFile } from '../utils/config.js';
5
+ import { LANGUAGES, LANGUAGE_NAMES, setLanguage, t, } from '../utils/i18n.js';
6
+ import { ACCENT } from './Frame.js';
7
+ const ROWS = [
8
+ {
9
+ key: 'language',
10
+ label: 'Language',
11
+ options: LANGUAGES,
12
+ display: value => LANGUAGE_NAMES[value],
13
+ },
14
+ {
15
+ key: 'introAnimation',
16
+ label: 'Intro animation',
17
+ options: ['on', 'off'],
18
+ display: value => t(value),
19
+ },
20
+ ];
21
+ /** Global preferences, saved per field like the Config tab. */
22
+ export function SettingsScreen({ onChanged, onClose, onOpenWorkspace, }) {
23
+ const [values, setValues] = useState(() => {
24
+ const s = getSettings();
25
+ return {
26
+ language: s.language,
27
+ introAnimation: s.introAnimation ? 'on' : 'off',
28
+ };
29
+ });
30
+ const [cursor, setCursor] = useState(0);
31
+ const [editing, setEditing] = useState(false);
32
+ const [draft, setDraft] = useState('');
33
+ const [note, setNote] = useState('');
34
+ const rowCount = ROWS.length + (onOpenWorkspace ? 1 : 0);
35
+ const onWorkspaceRow = cursor === ROWS.length;
36
+ const current = ROWS[Math.min(cursor, ROWS.length - 1)];
37
+ const commit = (value) => {
38
+ const next = { ...values, [current.key]: value };
39
+ setValues(next);
40
+ if (current.key === 'language')
41
+ setLanguage(value);
42
+ setSettings({
43
+ language: next.language,
44
+ introAnimation: next.introAnimation === 'on',
45
+ });
46
+ setNote(t('Saved.'));
47
+ onChanged();
48
+ };
49
+ useInput((input, key) => {
50
+ if (editing) {
51
+ if (key.escape) {
52
+ setEditing(false);
53
+ }
54
+ else if (key.return) {
55
+ setEditing(false);
56
+ if (draft !== values[current.key])
57
+ commit(draft);
58
+ }
59
+ else {
60
+ const step = key.leftArrow || key.upArrow
61
+ ? -1
62
+ : key.rightArrow || key.downArrow || input === ' '
63
+ ? 1
64
+ : 0;
65
+ if (step !== 0) {
66
+ const i = current.options.indexOf(draft);
67
+ const n = current.options.length;
68
+ setDraft(current.options[(i + step + n) % n]);
69
+ }
70
+ }
71
+ return;
72
+ }
73
+ if (key.escape)
74
+ onClose();
75
+ else if (key.downArrow || key.tab)
76
+ setCursor(c => (c + 1) % rowCount);
77
+ else if (key.upArrow)
78
+ setCursor(c => (c - 1 + rowCount) % rowCount);
79
+ else if (key.return) {
80
+ if (onWorkspaceRow) {
81
+ onOpenWorkspace?.();
82
+ return;
83
+ }
84
+ setDraft(values[current.key]);
85
+ setEditing(true);
86
+ }
87
+ });
88
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t('Settings') }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", t('stored in {file}', { file: settingsFile() })] })] }), ROWS.map((row, i) => {
89
+ const focused = i === cursor && !onWorkspaceRow;
90
+ const typing = focused && editing;
91
+ const chosen = typing ? draft : values[row.key];
92
+ return (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: focused ? ACCENT : undefined, bold: focused, dimColor: !focused, children: (focused ? '▸ ' : ' ') + t(row.label).padEnd(18) }), _jsx(Text, { children: row.options.map((option, j) => (_jsxs(Text, { children: [_jsx(Text, { bold: option === chosen, color: option === chosen ? ACCENT : undefined, inverse: typing && option === chosen, dimColor: option !== chosen, children: row.display(option) }), j < row.options.length - 1 && _jsx(Text, { dimColor: true, children: " \u00B7 " })] }, option))) })] }, row.key));
93
+ }), onOpenWorkspace && (_jsxs(Box, { height: 1, children: [_jsx(Text, { color: onWorkspaceRow ? ACCENT : undefined, bold: onWorkspaceRow, dimColor: !onWorkspaceRow, children: (onWorkspaceRow ? '▸ ' : ' ') + t('Workspace config').padEnd(18) }), _jsx(Text, { dimColor: true, children: t('shared auth and site config for every app') })] })), _jsx(Text, { color: "yellow", children: note }), _jsx(Text, { dimColor: true, children: editing
94
+ ? t('←→ choose · Enter confirm · Esc cancel')
95
+ : t('↑↓ setting · Enter edit · Esc close') })] }));
96
+ }
@@ -0,0 +1,10 @@
1
+ import type { ProjectInfo } from '../types/index.js';
2
+ interface Props {
3
+ apps: ProjectInfo[];
4
+ workspaceRoot?: string;
5
+ version: string;
6
+ minimal?: boolean;
7
+ updatedFrom?: string;
8
+ }
9
+ export declare function Shell({ apps: initialApps, workspaceRoot, version, minimal, updatedFrom, }: Props): import("react").JSX.Element;
10
+ export {};