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

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 +57 -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 +9 -0
  31. package/dist/shell/Help.js +95 -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 +622 -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 +314 -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,9 @@
1
+ import { type Hint } from './Frame.js';
2
+ import type { Tab } from './actions.js';
3
+ export type Where = Tab | 'nav' | 'workspace';
4
+ export declare function HelpPanel({ here, where, height, onClose, }: {
5
+ here: Hint[];
6
+ where: Where;
7
+ height: number;
8
+ onClose: () => void;
9
+ }): import("react").JSX.Element;
@@ -0,0 +1,95 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { Box, Text, measureElement, useInput } from 'ink';
4
+ import { t } from '../utils/i18n.js';
5
+ import { ACCENT } from './Frame.js';
6
+ import { actions } from './actions.js';
7
+ import { GROUPS } from './CommandPalette.js';
8
+ // What the pane you are in does, beyond the key list. One entry per paragraph.
9
+ const GUIDE = {
10
+ nav: {
11
+ title: 'Navigator',
12
+ text: [
13
+ 'Typing filters the app list. ↑↓ move, Enter selects the app and moves focus to the content pane. Action keys such as d or p only work there; in the navigator they search.',
14
+ 'The last row is Workspace settings: the shared root config that every app inherits.',
15
+ ],
16
+ },
17
+ workspace: {
18
+ title: 'Workspace settings',
19
+ text: [
20
+ "The root .dev_properties.json shared by every app, with the root package.json defaults underneath. Fill in site, auth and environments once; an app's own config overrides only what differs.",
21
+ 'Same keys as the Config tab: Enter edits and saves, Esc cancels, ←→ or space cycles choices.',
22
+ ],
23
+ },
24
+ overview: {
25
+ title: 'Overview',
26
+ text: [
27
+ 'Each status line names the key that fixes it: i installs dependencies, e opens Config, y syncs package.json, / sets up signing.',
28
+ 'The badge in the top bar is the active environment: green for the base, yellow for others, red for production. v cycles it.',
29
+ ],
30
+ },
31
+ config: {
32
+ title: 'Config',
33
+ text: [
34
+ 'Values come in layers and the source is shown next to each: your local .dev_properties.json wins, shared package.json values sit underneath, and in a workspace the root config underneath that. Environments add on top of the base and override only what differs.',
35
+ 'Enter edits and saves a field, Esc cancels, ←→ or space cycles choices, Ctrl+O lists the addons on the site. y copies shared values into package.json for the team.',
36
+ 'Passwords and secrets go to the OS keychain, never to a file. Leave them empty to be asked on each run.',
37
+ ],
38
+ },
39
+ versions: {
40
+ title: 'Versions',
41
+ text: [
42
+ "The versions uploaded to the addon on the active environment's site, the domain in the top bar. v switches environment.",
43
+ '↑↓ select, a activates the selected version, r refreshes. Production deploys activate on their own.',
44
+ ],
45
+ },
46
+ log: {
47
+ title: 'Log',
48
+ text: [
49
+ 'Output from build, sign, deploy, dev and watch for the selected app. Dev and watch keep running while you switch apps; K stops them.',
50
+ '↑↓ scroll, PgUp/PgDn page, f jumps to the end, x toggles line wrap.',
51
+ ],
52
+ },
53
+ };
54
+ const MOVE = [
55
+ ['Tab', 'switch pane'],
56
+ ['1–4', 'tabs'],
57
+ ['←→', 'tabs'],
58
+ ['/', 'commands'],
59
+ ['?', 'help'],
60
+ ['Esc', 'back'],
61
+ ];
62
+ function Row({ keys, label }) {
63
+ return (_jsxs(Text, { wrap: "truncate", children: [_jsx(Text, { bold: true, color: ACCENT, children: keys.padEnd(6) }), label] }));
64
+ }
65
+ export function HelpPanel({ here, where, height, onClose, }) {
66
+ const inner = useRef(null);
67
+ const [top, setTop] = useState(0);
68
+ const [total, setTotal] = useState(0);
69
+ useEffect(() => {
70
+ if (inner.current)
71
+ setTotal(measureElement(inner.current).height);
72
+ });
73
+ const visible = Math.max(1, height - 1);
74
+ const max = Math.max(0, total - visible);
75
+ useInput((input, key) => {
76
+ if (key.escape || key.return || input === '?' || input === 'q')
77
+ onClose();
78
+ else if (key.upArrow)
79
+ setTop(n => Math.max(0, n - 1));
80
+ else if (key.downArrow)
81
+ setTop(n => Math.min(max, n + 1));
82
+ else if (key.pageUp)
83
+ setTop(n => Math.max(0, n - visible));
84
+ else if (key.pageDown)
85
+ setTop(n => Math.min(max, n + visible));
86
+ });
87
+ // Only keys not already listed elsewhere, e.g. f and x on the Log tab.
88
+ const local = here.filter(hint => actions.every(action => action.key !== hint.key) &&
89
+ MOVE.every(([keys]) => keys !== hint.key));
90
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, height: height, overflow: "hidden", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: t(GUIDE[where].title) }), _jsxs(Text, { dimColor: true, children: [' · ', max > 0 ? t('↑↓ scroll · Esc close') : t('Esc close')] })] }), _jsx(Box, { flexDirection: "column", overflow: "hidden", height: visible, children: _jsxs(Box, { ref: inner, flexDirection: "column", flexShrink: 0, marginTop: -top, children: [GUIDE[where].text.map(line => (_jsx(Text, { wrap: "wrap", children: t(line) }, line))), _jsx(Box, { marginTop: 1, children: _jsx(Text, { bold: true, children: t('Keys') }) }), _jsxs(Box, { flexWrap: "wrap", columnGap: 4, children: [_jsx(Box, { flexDirection: "column", 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
91
+ ? [
92
+ _jsx(Row, { keys: action.key, label: t(action.label) }, action.id),
93
+ ]
94
+ : [])] }, group.id))) }), _jsxs(Box, { flexDirection: "column", 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)))] })] })] })] }) })] }));
95
+ }
@@ -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 {};