@polterware/polterbase 0.1.2 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +30 -11
  2. package/dist/index.js +1638 -196
  3. package/package.json +9 -5
package/dist/index.js CHANGED
@@ -1,225 +1,1667 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/index.ts
4
- import * as p from "@clack/prompts";
3
+ // src/index.tsx
4
+ import React7 from "react";
5
+ import { render } from "ink";
6
+
7
+ // src/app.tsx
8
+ import { Box as Box13, Text as Text12, useApp } from "ink";
9
+ import pc2 from "picocolors";
10
+
11
+ // src/hooks/useNavigation.ts
12
+ import { useState, useCallback } from "react";
13
+ function useNavigation() {
14
+ const [state, setState] = useState({
15
+ screen: "main-menu",
16
+ params: {}
17
+ });
18
+ const navigate = useCallback((screen, params) => {
19
+ setState({
20
+ screen,
21
+ params: params ?? {}
22
+ });
23
+ }, []);
24
+ const goBack = useCallback(() => {
25
+ setState({ screen: "main-menu", params: {} });
26
+ }, []);
27
+ return { screen: state.screen, params: state.params, navigate, goBack };
28
+ }
29
+
30
+ // src/screens/MainMenu.tsx
31
+ import { useEffect as useEffect2, useMemo as useMemo2, useState as useState3 } from "react";
32
+ import { Box as Box4, Text as Text4 } from "ink";
33
+
34
+ // src/components/GhostBanner.tsx
35
+ import { Box, Text } from "ink";
36
+
37
+ // src/theme.ts
5
38
  import pc from "picocolors";
6
- import { spawn } from "child_process";
7
- import Conf from "conf";
8
- var config = new Conf({ projectName: "polterbase" });
9
- var supabaseCommands = {
10
- "Quick Start": [
11
- { value: "bootstrap", label: "bootstrap - Bootstrap a Supabase project from a starter template" }
12
- ],
13
- "Local Development": [
14
- { value: "db", label: "db - Manage Postgres databases" },
15
- { value: "gen", label: "gen - Run code generation tools" },
16
- { value: "init", label: "init - Initialize a local project" },
17
- { value: "inspect", label: "inspect - Tools to inspect your Supabase project" },
18
- { value: "link", label: "link - Link to a Supabase project" },
19
- { value: "login", label: "login - Authenticate using an access token" },
20
- { value: "logout", label: "logout - Log out and delete access tokens locally" },
21
- { value: "migration", label: "migration - Manage database migration scripts" },
22
- { value: "seed", label: "seed - Seed a Supabase project from supabase/config.toml" },
23
- { value: "services", label: "services - Show versions of all Supabase services" },
24
- { value: "start", label: "start - Start containers for Supabase local development" },
25
- { value: "status", label: "status - Show status of local Supabase containers" },
26
- { value: "stop", label: "stop - Stop all local Supabase containers" },
27
- { value: "test", label: "test - Run tests on local Supabase containers" },
28
- { value: "unlink", label: "unlink - Unlink a Supabase project" }
29
- ],
30
- "Management APIs": [
31
- { value: "backups", label: "backups - Manage Supabase physical backups" },
32
- { value: "branches", label: "branches - Manage Supabase preview branches" },
33
- { value: "config", label: "config - Manage Supabase project configurations" },
34
- { value: "domains", label: "domains - Manage custom domain names for Supabase projects" },
35
- { value: "encryption", label: "encryption - Manage encryption keys of Supabase projects" },
36
- { value: "functions", label: "functions - Manage Supabase Edge functions" },
37
- { value: "network-bans", label: "network-bans - Manage network bans" },
38
- { value: "network-restrictions", label: "network-restrictions - Manage network restrictions" },
39
- { value: "orgs", label: "orgs - Manage Supabase organizations" },
40
- { value: "postgres-config", label: "postgres-config - Manage Postgres database config" },
41
- { value: "projects", label: "projects - Manage Supabase projects" },
42
- { value: "secrets", label: "secrets - Manage Supabase secrets" },
43
- { value: "snippets", label: "snippets - Manage Supabase SQL snippets" },
44
- { value: "ssl-enforcement", label: "ssl-enforcement - Manage SSL enforcement configuration" },
45
- { value: "sso", label: "sso - Manage Single Sign-On (SSO) authentication for projects" },
46
- { value: "storage", label: "storage - Manage Supabase Storage objects" },
47
- { value: "vanity-subdomains", label: "vanity-subdomains - Manage vanity subdomains for Supabase projects" }
48
- ],
49
- "Additional Commands": [
50
- { value: "completion", label: "completion - Generate the autocompletion script" },
51
- { value: "help", label: "help - Help about any command" }
39
+ var VERSION = "0.1.2";
40
+ var colors = {
41
+ primary: pc.cyan,
42
+ primaryBold: (s) => pc.bold(pc.cyan(s)),
43
+ accent: pc.magenta,
44
+ accentBold: (s) => pc.bold(pc.magenta(s)),
45
+ success: pc.green,
46
+ successBold: (s) => pc.bold(pc.green(s)),
47
+ error: pc.red,
48
+ errorBold: (s) => pc.bold(pc.red(s)),
49
+ warning: pc.yellow,
50
+ warningBold: (s) => pc.bold(pc.yellow(s)),
51
+ dim: pc.dim,
52
+ bold: pc.bold,
53
+ white: pc.white,
54
+ highlight: (s) => pc.bgCyan(pc.black(pc.bold(s)))
55
+ };
56
+ var ghost = {
57
+ art: [
58
+ " \u2584\u2584\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584\u2584",
59
+ " \u2588 \u2588",
60
+ " \u2588 \u2584\u2588\u2588\u2584 \u2582\u2588\u2582 \u2588",
61
+ " \u2588 \u2580\u2588\u2588\u2580 \u2594\u2588\u2594 \u2588",
62
+ " \u2588 \u2588",
63
+ " \u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588",
64
+ " \u2588 \u2588",
65
+ " \u2588\u2584\u2588\u2588\u2580\u2580\u2588\u2588\u2584\u2584\u2584\u2584\u2588\u2588\u2580\u2580\u2588\u2588\u2584\u2588"
52
66
  ]
53
67
  };
54
- var globalFlags = [
55
- { value: "--create-ticket", label: "--create-ticket (Create support ticket on error)" },
56
- { value: "--debug", label: "--debug (Output debug logs)" },
57
- { value: "--experimental", label: "--experimental (Enable experimental features)" },
58
- { value: "--yes", label: "--yes (Answer yes to all prompts)" }
59
- ];
60
- function getPinnedCommands() {
61
- return config.get("pinnedCommands") || [];
68
+
69
+ // src/components/GhostBanner.tsx
70
+ import { jsx, jsxs } from "react/jsx-runtime";
71
+ function GhostBanner() {
72
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", alignItems: "flex-start", gap: 2, marginBottom: 1, children: [
73
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: ghost.art.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "cyan", children: line }, i)) }),
74
+ /* @__PURE__ */ jsxs(
75
+ Box,
76
+ {
77
+ flexDirection: "column",
78
+ borderStyle: "single",
79
+ borderColor: "cyan",
80
+ paddingX: 1,
81
+ children: [
82
+ /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: " POLTERBASE " }),
83
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
84
+ "Version: ",
85
+ VERSION
86
+ ] }),
87
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "The modern interactive CLI for Supabase" })
88
+ ]
89
+ }
90
+ )
91
+ ] });
62
92
  }
63
- function addPinnedCommand(cmd) {
64
- const current = getPinnedCommands();
65
- if (!current.includes(cmd)) {
66
- config.set("pinnedCommands", [...current, cmd]);
93
+
94
+ // src/components/SelectList.tsx
95
+ import { useEffect, useMemo, useState as useState2 } from "react";
96
+ import { Box as Box2, Text as Text2, useInput } from "ink";
97
+
98
+ // src/components/selectListSections.ts
99
+ function isHeader(item) {
100
+ return item?.kind === "header";
101
+ }
102
+ function buildEntryKey(item, globalIndex) {
103
+ return item.id ?? `${item.value}-${globalIndex}`;
104
+ }
105
+ function findNearestHeaderLabel(items, startIndex) {
106
+ for (let index = startIndex; index >= 0; index -= 1) {
107
+ const item = items[index];
108
+ if (isHeader(item)) {
109
+ return item?.label;
110
+ }
67
111
  }
112
+ return void 0;
68
113
  }
69
- function removePinnedCommand(cmd) {
70
- const current = getPinnedCommands();
71
- config.set("pinnedCommands", current.filter((c) => c !== cmd));
72
- }
73
- async function main() {
74
- console.clear();
75
- p.intro(`${pc.bgGreen(pc.black(" POLTERBASE "))} The ultimate modern CLI for Supabase`);
76
- let shouldExit = false;
77
- while (!shouldExit) {
78
- const pinned = getPinnedCommands();
79
- const mainOptions = [
80
- { value: "Quick Start", label: "Quick Start (Bootstrap templates)" },
81
- { value: "Local Development", label: "Local Development (db, init, start, etc.)" },
82
- { value: "Management APIs", label: "Management APIs (projects, secrets, domains, etc.)" },
83
- { value: "Additional Commands", label: "Additional Commands (help, completion)" },
84
- { value: "Custom", label: "Custom Command / Check Version" }
85
- ];
86
- if (pinned.length > 0) {
87
- mainOptions.unshift(
88
- { value: "MANAGE_PINS", label: pc.magenta("\u2699\uFE0F Manage Pinned Commands") },
89
- ...pinned.map((cmd) => ({ value: `PIN:${cmd}`, label: `${pc.cyan("\u{1F4CC}")} ${cmd}` })),
90
- // @ts-ignore
91
- { value: "SEPARATOR", label: pc.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"), disabled: true }
92
- );
114
+ function buildBoxedSectionLayout(items, start, end) {
115
+ const visibleEntries = items.slice(start, end).map((item, offset) => ({
116
+ item,
117
+ globalIndex: start + offset
118
+ }));
119
+ const inheritedHeader = visibleEntries.length > 0 && !isHeader(visibleEntries[0]?.item) ? findNearestHeaderLabel(items, start - 1) : void 0;
120
+ const sections = [];
121
+ let currentTitle = inheritedHeader;
122
+ let currentKey = currentTitle ? `derived-${start}` : void 0;
123
+ let currentRows = [];
124
+ const flush = () => {
125
+ if (!currentTitle && currentRows.length === 0) {
126
+ return;
93
127
  }
94
- mainOptions.push({ value: "EXIT", label: pc.red("Exit Polterbase") });
95
- const category = await p.select({
96
- message: "What kind of Supabase command do you want to run?",
97
- // @ts-ignore
98
- options: mainOptions.filter((opt) => opt.value !== "SEPARATOR"),
99
- maxItems: 12
100
- });
101
- if (p.isCancel(category) || category === "EXIT") {
102
- p.cancel("See you later!");
103
- process.exit(0);
104
- }
105
- if (category === "MANAGE_PINS") {
106
- const toRemove = await p.multiselect({
107
- message: "Select commands to remove from pins (Space to select):",
108
- options: pinned.map((cmd) => ({ value: cmd, label: cmd })),
109
- required: false
128
+ if (currentRows.length === 0) {
129
+ sections.push({
130
+ type: "heading",
131
+ key: currentKey ?? `heading-${sections.length}`,
132
+ label: currentTitle ?? ""
110
133
  });
111
- if (!p.isCancel(toRemove) && Array.isArray(toRemove)) {
112
- toRemove.forEach((cmd) => removePinnedCommand(cmd));
113
- p.log.success("Pins updated!");
114
- }
134
+ } else {
135
+ sections.push({
136
+ type: "box",
137
+ key: currentKey ?? `box-${sections.length}`,
138
+ title: currentTitle ?? "",
139
+ rows: [...currentRows]
140
+ });
141
+ }
142
+ currentTitle = void 0;
143
+ currentKey = void 0;
144
+ currentRows = [];
145
+ };
146
+ for (const entry of visibleEntries) {
147
+ if (isHeader(entry.item)) {
148
+ flush();
149
+ currentTitle = entry.item.label;
150
+ currentKey = buildEntryKey(entry.item, entry.globalIndex);
115
151
  continue;
116
152
  }
117
- let finalArgs = [];
118
- let isPinnedExecution = false;
119
- if (typeof category === "string" && category.startsWith("PIN:")) {
120
- const cmdStr = category.replace("PIN:", "");
121
- finalArgs = cmdStr.split(" ");
122
- isPinnedExecution = true;
123
- } else {
124
- let commandValue = "";
125
- if (category === "Custom") {
126
- const custom = await p.text({
127
- message: 'Enter your custom supabase command/flags (e.g., "-v" or "status -o json"):',
128
- placeholder: "-v"
129
- });
130
- if (p.isCancel(custom)) continue;
131
- commandValue = custom;
132
- } else {
133
- const options = [
134
- { value: "BACK", label: pc.yellow("\u2190 Go Back") },
135
- // @ts-ignore
136
- ...supabaseCommands[category]
137
- ];
138
- const command = await p.select({
139
- message: `Select a command from ${category}:`,
140
- options,
141
- maxItems: 11
142
- });
143
- if (p.isCancel(command) || command === "BACK") continue;
144
- const extraArgs = await p.text({
145
- message: `Any additional sub-commands or arguments for "supabase ${command}"?`,
146
- placeholder: 'e.g., "push", "pull", "list" (Press Enter to skip)',
147
- initialValue: ""
148
- });
149
- if (p.isCancel(extraArgs)) continue;
150
- commandValue = `${command} ${extraArgs}`.trim();
151
- }
152
- const selectedFlags = await p.multiselect({
153
- message: "Select any global flags you want to include (Space to select, Enter to confirm):",
154
- options: globalFlags,
155
- required: false
153
+ if (!currentTitle) {
154
+ currentTitle = "";
155
+ currentKey = buildEntryKey(entry.item, entry.globalIndex);
156
+ }
157
+ currentRows.push(entry);
158
+ }
159
+ flush();
160
+ return sections.filter(
161
+ (section) => section.type === "box" ? section.rows.length > 0 : Boolean(section.label)
162
+ );
163
+ }
164
+ function countBoxedSectionLines(sections) {
165
+ return sections.reduce((lineCount, section) => {
166
+ if (section.type === "heading") {
167
+ return lineCount + 1;
168
+ }
169
+ return lineCount + section.rows.length + (section.title ? 3 : 2);
170
+ }, 0);
171
+ }
172
+
173
+ // src/components/SelectList.tsx
174
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
175
+ function SelectList({
176
+ items,
177
+ onSelect,
178
+ onRightAction,
179
+ onCancel,
180
+ maxVisible = 16,
181
+ labelWidth = 34,
182
+ boxedSections = false
183
+ }) {
184
+ const isHeader2 = (item) => item.kind === "header";
185
+ const isSelectable = (item) => item.selectable ?? !isHeader2(item);
186
+ const isPinnedRow = (item) => item.section === "pinned-runs" || item.section === "pinned-commands";
187
+ const selectableIndexes = useMemo(
188
+ () => items.reduce((acc, item, index) => {
189
+ if (isSelectable(item)) {
190
+ acc.push(index);
191
+ }
192
+ return acc;
193
+ }, []),
194
+ [items]
195
+ );
196
+ const [selectedSelectableIndex, setSelectedSelectableIndex] = useState2(0);
197
+ useEffect(() => {
198
+ if (selectableIndexes.length === 0) {
199
+ setSelectedSelectableIndex(0);
200
+ return;
201
+ }
202
+ setSelectedSelectableIndex((prev) => {
203
+ if (prev < 0) return 0;
204
+ if (prev >= selectableIndexes.length) return selectableIndexes.length - 1;
205
+ return prev;
206
+ });
207
+ }, [selectableIndexes]);
208
+ const selectedItemIndex = selectableIndexes[selectedSelectableIndex] ?? -1;
209
+ const selectedItem = selectedItemIndex >= 0 ? items[selectedItemIndex] : void 0;
210
+ useInput((input, key) => {
211
+ if (selectableIndexes.length === 0) {
212
+ if (key.escape && onCancel) {
213
+ onCancel();
214
+ }
215
+ return;
216
+ }
217
+ if (key.upArrow || input === "k") {
218
+ setSelectedSelectableIndex((prev) => {
219
+ let next = prev - 1;
220
+ if (next < 0) next = selectableIndexes.length - 1;
221
+ return next;
222
+ });
223
+ }
224
+ if (key.downArrow || input === "j") {
225
+ setSelectedSelectableIndex((prev) => {
226
+ let next = prev + 1;
227
+ if (next >= selectableIndexes.length) next = 0;
228
+ return next;
156
229
  });
157
- if (p.isCancel(selectedFlags)) continue;
158
- finalArgs = commandValue.split(" ").filter(Boolean);
159
- if (Array.isArray(selectedFlags) && selectedFlags.length > 0) {
160
- finalArgs = finalArgs.concat(selectedFlags);
230
+ }
231
+ if (key.return) {
232
+ if (selectedItem) {
233
+ onSelect(selectedItem.value, selectedItem);
234
+ }
235
+ }
236
+ if (key.rightArrow && onRightAction && selectedItem?.rightActionable) {
237
+ onRightAction(selectedItem);
238
+ }
239
+ if (key.escape && onCancel) {
240
+ onCancel();
241
+ }
242
+ });
243
+ const getWindowStart = () => {
244
+ if (items.length <= maxVisible) {
245
+ return 0;
246
+ }
247
+ const safeSelectedIndex = selectedItemIndex >= 0 ? selectedItemIndex : 0;
248
+ let start = Math.max(
249
+ 0,
250
+ Math.min(
251
+ safeSelectedIndex - Math.floor(maxVisible / 2),
252
+ items.length - maxVisible
253
+ )
254
+ );
255
+ const firstVisible = items[start];
256
+ if (start > 0 && firstVisible && !isHeader2(firstVisible)) {
257
+ let previousHeaderIndex = -1;
258
+ for (let i = start - 1; i >= 0; i--) {
259
+ if (isHeader2(items[i])) {
260
+ previousHeaderIndex = i;
261
+ break;
262
+ }
263
+ }
264
+ if (previousHeaderIndex >= 0 && safeSelectedIndex - previousHeaderIndex < maxVisible) {
265
+ start = previousHeaderIndex;
161
266
  }
162
267
  }
163
- const finalCmdString = `supabase ${finalArgs.join(" ")}`;
164
- const confirm2 = await p.confirm({
165
- message: `Ready to execute: ${pc.cyan(finalCmdString)}. Continue?`,
166
- initialValue: true
268
+ return start;
269
+ };
270
+ const getWindowRange = () => {
271
+ const initialStart = getWindowStart();
272
+ const initialEnd = Math.min(initialStart + maxVisible, items.length);
273
+ if (!boxedSections) {
274
+ return [initialStart, initialEnd];
275
+ }
276
+ let start = initialStart;
277
+ let end = initialEnd;
278
+ while (start < end) {
279
+ const sections = buildBoxedSectionLayout(items, start, end);
280
+ if (countBoxedSectionLines(sections) <= maxVisible) {
281
+ break;
282
+ }
283
+ if (selectedItemIndex >= 0 && start === selectedItemIndex && end === selectedItemIndex + 1) {
284
+ break;
285
+ }
286
+ const canTrimStart = selectedItemIndex > start;
287
+ const canTrimEnd = selectedItemIndex < end - 1;
288
+ if (canTrimEnd && (!canTrimStart || end - selectedItemIndex >= selectedItemIndex - start)) {
289
+ end -= 1;
290
+ continue;
291
+ }
292
+ if (canTrimStart) {
293
+ start += 1;
294
+ continue;
295
+ }
296
+ end -= 1;
297
+ }
298
+ return [start, end];
299
+ };
300
+ const [windowStart, windowEnd] = getWindowRange();
301
+ const visibleItems = items.slice(windowStart, windowEnd);
302
+ const boxedLayout = boxedSections ? buildBoxedSectionLayout(items, windowStart, windowEnd) : [];
303
+ const showScrollUp = windowStart > 0;
304
+ const showScrollDown = windowEnd < items.length;
305
+ const renderSelectableRow = (item, globalIdx) => {
306
+ const isSelected = globalIdx === selectedItemIndex;
307
+ return /* @__PURE__ */ jsxs2(Box2, { gap: 1, children: [
308
+ /* @__PURE__ */ jsx2(Text2, { color: isSelected ? "cyan" : void 0, children: isSelected ? "\u276F" : " " }),
309
+ /* @__PURE__ */ jsx2(Box2, { width: labelWidth, children: /* @__PURE__ */ jsxs2(
310
+ Text2,
311
+ {
312
+ color: isSelected ? "cyan" : isPinnedRow(item) ? "white" : void 0,
313
+ bold: isSelected || isPinnedRow(item),
314
+ children: [
315
+ item.icon ? `${item.icon} ` : "",
316
+ item.label
317
+ ]
318
+ }
319
+ ) }),
320
+ item.hint && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: item.hint }),
321
+ isSelected && item.rightActionable && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "\u2192 pin/unpin" })
322
+ ] }, item.id ?? `${item.value}-${globalIdx}`);
323
+ };
324
+ return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
325
+ showScrollUp && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2191 more" }),
326
+ boxedSections ? boxedLayout.map((section) => {
327
+ if (section.type === "heading") {
328
+ return /* @__PURE__ */ jsx2(Box2, { children: /* @__PURE__ */ jsx2(Text2, { color: "yellow", bold: true, children: section.label }) }, section.key);
329
+ }
330
+ const hasSelectedRow = section.rows.some(
331
+ (row) => row.globalIndex === selectedItemIndex
332
+ );
333
+ const isPinnedSection = section.rows.some(
334
+ (row) => isPinnedRow(row.item)
335
+ );
336
+ return /* @__PURE__ */ jsxs2(
337
+ Box2,
338
+ {
339
+ flexDirection: "column",
340
+ borderStyle: "round",
341
+ borderColor: hasSelectedRow ? "cyan" : isPinnedSection ? "green" : "yellow",
342
+ paddingX: 1,
343
+ children: [
344
+ section.title && /* @__PURE__ */ jsx2(Text2, { color: hasSelectedRow ? "cyan" : "yellow", bold: true, children: section.title }),
345
+ /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", children: section.rows.map(
346
+ (row) => renderSelectableRow(row.item, row.globalIndex)
347
+ ) })
348
+ ]
349
+ },
350
+ section.key
351
+ );
352
+ }) : visibleItems.map((item, i) => {
353
+ const globalIdx = windowStart + i;
354
+ const selectable = isSelectable(item);
355
+ if (!selectable) {
356
+ return /* @__PURE__ */ jsx2(
357
+ Box2,
358
+ {
359
+ marginTop: i === 0 ? 0 : 1,
360
+ children: /* @__PURE__ */ jsx2(Text2, { color: "yellow", bold: true, children: item.label })
361
+ },
362
+ item.id ?? `${item.value}-${globalIdx}`
363
+ );
364
+ }
365
+ return renderSelectableRow(item, globalIdx);
366
+ }),
367
+ showScrollDown && /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: " \u2193 more" })
368
+ ] });
369
+ }
370
+
371
+ // src/components/StatusBar.tsx
372
+ import { Box as Box3, Text as Text3 } from "ink";
373
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
374
+ function StatusBar({ hint }) {
375
+ return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, justifyContent: "space-between", children: [
376
+ /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: hint || "\u2191\u2193 navigate \xB7 Enter select \xB7 Esc back" }),
377
+ /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
378
+ "polterbase v",
379
+ VERSION
380
+ ] })
381
+ ] });
382
+ }
383
+
384
+ // src/data/pins.ts
385
+ import Conf from "conf";
386
+ var config = new Conf({
387
+ projectName: process.env.NODE_ENV === "test" ? "polterbase-test" : "polterbase"
388
+ });
389
+ var COMMAND_PINS_KEY = "pinnedCommandBasesV2";
390
+ var RUN_PINS_KEY = "pinnedCommandRunsV1";
391
+ var LEGACY_PINS_KEY = "pinnedCommands";
392
+ function ensurePinsInitialized() {
393
+ if (!config.has(COMMAND_PINS_KEY)) {
394
+ config.set(COMMAND_PINS_KEY, []);
395
+ }
396
+ if (!config.has(RUN_PINS_KEY)) {
397
+ config.set(RUN_PINS_KEY, []);
398
+ }
399
+ if (config.has(LEGACY_PINS_KEY)) {
400
+ config.delete(LEGACY_PINS_KEY);
401
+ }
402
+ }
403
+ function getPinnedCommands() {
404
+ ensurePinsInitialized();
405
+ return config.get(COMMAND_PINS_KEY) || [];
406
+ }
407
+ function setPinnedCommands(commands) {
408
+ config.set(COMMAND_PINS_KEY, commands);
409
+ }
410
+ function togglePinnedCommand(cmd) {
411
+ ensurePinsInitialized();
412
+ const current = getPinnedCommands();
413
+ if (current.includes(cmd)) {
414
+ setPinnedCommands(current.filter((c) => c !== cmd));
415
+ return;
416
+ }
417
+ setPinnedCommands([cmd, ...current.filter((c) => c !== cmd)]);
418
+ }
419
+ function getPinnedRuns() {
420
+ ensurePinsInitialized();
421
+ return config.get(RUN_PINS_KEY) || [];
422
+ }
423
+ function setPinnedRuns(runs) {
424
+ config.set(RUN_PINS_KEY, runs);
425
+ }
426
+ function togglePinnedRun(runCommand) {
427
+ ensurePinsInitialized();
428
+ const current = getPinnedRuns();
429
+ if (current.includes(runCommand)) {
430
+ setPinnedRuns(current.filter((run) => run !== runCommand));
431
+ return;
432
+ }
433
+ setPinnedRuns([runCommand, ...current.filter((run) => run !== runCommand)]);
434
+ }
435
+ function isPinnedRun(runCommand) {
436
+ return getPinnedRuns().includes(runCommand);
437
+ }
438
+
439
+ // src/data/commands.ts
440
+ var categories = {
441
+ "quick-start": {
442
+ icon: "\u{1F680}",
443
+ label: "Quick Start",
444
+ description: "Get started with Supabase",
445
+ commands: [
446
+ {
447
+ value: "bootstrap",
448
+ label: "bootstrap",
449
+ hint: "Bootstrap from a starter template"
450
+ },
451
+ { value: "init", label: "init", hint: "Initialize a local project" },
452
+ {
453
+ value: "login",
454
+ label: "login",
455
+ hint: "Authenticate with access token"
456
+ },
457
+ { value: "logout", label: "logout", hint: "Remove local auth token" }
458
+ ]
459
+ },
460
+ "local-dev": {
461
+ icon: "\u{1F6E0}",
462
+ label: "Local Dev",
463
+ description: "Day-to-day local workflow",
464
+ commands: [
465
+ {
466
+ value: "start",
467
+ label: "start",
468
+ hint: "Start local Supabase containers"
469
+ },
470
+ { value: "stop", label: "stop", hint: "Stop local Supabase containers" },
471
+ { value: "status", label: "status", hint: "Show container status" },
472
+ { value: "db", label: "db", hint: "Manage Postgres databases" },
473
+ {
474
+ value: "migration",
475
+ label: "migration",
476
+ hint: "Manage migration scripts"
477
+ },
478
+ { value: "seed", label: "seed", hint: "Seed from config.toml" },
479
+ { value: "test", label: "test", hint: "Run tests on local stack" }
480
+ ]
481
+ },
482
+ "inspect-generate": {
483
+ icon: "\u{1F50D}",
484
+ label: "Inspect & Generate",
485
+ description: "Tooling and introspection",
486
+ commands: [
487
+ { value: "inspect", label: "inspect", hint: "Inspect project resources" },
488
+ { value: "gen", label: "gen", hint: "Run code generation tools" },
489
+ { value: "services", label: "services", hint: "Show service versions" },
490
+ { value: "link", label: "link", hint: "Link to remote project" },
491
+ { value: "unlink", label: "unlink", hint: "Unlink remote project" }
492
+ ]
493
+ },
494
+ cloud: {
495
+ icon: "\u2601\uFE0F",
496
+ label: "Cloud Management",
497
+ description: "Core cloud resources",
498
+ commands: [
499
+ {
500
+ value: "projects",
501
+ label: "projects",
502
+ hint: "Manage Supabase projects"
503
+ },
504
+ { value: "functions", label: "functions", hint: "Manage Edge Functions" },
505
+ { value: "secrets", label: "secrets", hint: "Manage project secrets" },
506
+ {
507
+ value: "config",
508
+ label: "config",
509
+ hint: "Manage project configuration"
510
+ },
511
+ { value: "storage", label: "storage", hint: "Manage Storage objects" }
512
+ ]
513
+ },
514
+ networking: {
515
+ icon: "\u{1F310}",
516
+ label: "Networking & Security",
517
+ description: "Network and security config",
518
+ commands: [
519
+ { value: "domains", label: "domains", hint: "Manage custom domains" },
520
+ {
521
+ value: "ssl-enforcement",
522
+ label: "ssl-enforcement",
523
+ hint: "Manage SSL config"
524
+ },
525
+ {
526
+ value: "network-bans",
527
+ label: "network-bans",
528
+ hint: "Manage network bans"
529
+ },
530
+ {
531
+ value: "network-restrictions",
532
+ label: "network-restrictions",
533
+ hint: "Manage network restrictions"
534
+ },
535
+ {
536
+ value: "vanity-subdomains",
537
+ label: "vanity-subdomains",
538
+ hint: "Manage vanity subdomains"
539
+ },
540
+ {
541
+ value: "encryption",
542
+ label: "encryption",
543
+ hint: "Manage encryption keys"
544
+ }
545
+ ]
546
+ },
547
+ "org-auth": {
548
+ icon: "\u{1F465}",
549
+ label: "Organizations & Admin",
550
+ description: "Team management and admin tools",
551
+ commands: [
552
+ { value: "orgs", label: "orgs", hint: "Manage organizations" },
553
+ { value: "sso", label: "sso", hint: "Manage Single Sign-On" },
554
+ { value: "branches", label: "branches", hint: "Manage preview branches" },
555
+ { value: "backups", label: "backups", hint: "Manage physical backups" },
556
+ { value: "snippets", label: "snippets", hint: "Manage SQL snippets" },
557
+ {
558
+ value: "postgres-config",
559
+ label: "postgres-config",
560
+ hint: "Manage Postgres config"
561
+ }
562
+ ]
563
+ },
564
+ utilities: {
565
+ icon: "\u2699\uFE0F",
566
+ label: "Utilities",
567
+ description: "Help and shell completions",
568
+ commands: [
569
+ {
570
+ value: "completion",
571
+ label: "completion",
572
+ hint: "Generate shell completion script"
573
+ },
574
+ { value: "help", label: "help", hint: "Help about any command" }
575
+ ]
576
+ }
577
+ };
578
+
579
+ // src/screens/mainMenuModel.ts
580
+ var commandCatalog = Object.entries(categories).flatMap(
581
+ ([, category]) => category.commands.map((command) => ({
582
+ categoryIcon: category.icon,
583
+ categoryLabel: category.label,
584
+ value: command.value,
585
+ hint: command.hint
586
+ }))
587
+ );
588
+ var commandInfoByValue = new Map(
589
+ commandCatalog.map((entry) => [entry.value, entry])
590
+ );
591
+ function getCommandInfo(commandValue) {
592
+ return commandInfoByValue.get(commandValue);
593
+ }
594
+ function buildMainMenuItems({
595
+ pinnedCommands,
596
+ pinnedRuns
597
+ }) {
598
+ const pinnedCommandSet = new Set(pinnedCommands);
599
+ const nextItems = [];
600
+ if (pinnedRuns.length > 0) {
601
+ nextItems.push({
602
+ id: "section-pinned-runs",
603
+ value: "__section_pinned_runs__",
604
+ label: "\u25B6 Pinned Runs",
605
+ kind: "header",
606
+ selectable: false
167
607
  });
168
- if (p.isCancel(confirm2)) continue;
169
- if (!confirm2) {
170
- const action = await p.select({
171
- message: "Command cancelled. What next?",
172
- options: [
173
- { value: "RETRY", label: "Try another command" },
174
- { value: "EXIT", label: "Exit" }
175
- ]
608
+ for (const runCommand of pinnedRuns) {
609
+ const baseCommand = runCommand.split(" ").filter(Boolean)[0] ?? "";
610
+ const info = getCommandInfo(baseCommand);
611
+ const infoHint = info ? `${info.categoryIcon} ${info.categoryLabel} \xB7 exact pinned run` : "Exact pinned run";
612
+ nextItems.push({
613
+ id: `run:${runCommand}`,
614
+ value: runCommand,
615
+ label: runCommand,
616
+ hint: infoHint,
617
+ icon: "\u25B6",
618
+ kind: "run",
619
+ rightActionable: true,
620
+ section: "pinned-runs"
176
621
  });
177
- if (action === "RETRY") continue;
178
- break;
179
622
  }
180
- p.outro(`Executing: ${pc.cyan(finalCmdString)}`);
181
- const child = spawn("supabase", finalArgs, {
182
- stdio: "inherit",
183
- shell: true
623
+ }
624
+ if (pinnedCommands.length > 0) {
625
+ nextItems.push({
626
+ id: "section-pinned-commands",
627
+ value: "__section_pinned_commands__",
628
+ label: "\u{1F4CC} Pinned Commands",
629
+ kind: "header",
630
+ selectable: false
184
631
  });
185
- await new Promise((resolve) => {
186
- child.on("error", (err) => {
187
- console.error(pc.red(`Failed to start command: ${err.message}`));
188
- resolve();
632
+ for (const command of pinnedCommands) {
633
+ const info = getCommandInfo(command);
634
+ const infoHint = info ? `${info.categoryIcon} ${info.categoryLabel}${info.hint ? ` \xB7 ${info.hint}` : ""}` : "Pinned command";
635
+ nextItems.push({
636
+ id: `command:${command}`,
637
+ value: command,
638
+ label: command,
639
+ hint: infoHint,
640
+ icon: "\u{1F4CC}",
641
+ kind: "command",
642
+ rightActionable: true,
643
+ section: "pinned-commands"
189
644
  });
190
- child.on("exit", (code) => {
191
- if (code !== 0) {
192
- console.log(pc.red(`
193
- Command exited with code ${code}`));
194
- } else {
195
- console.log(pc.green("\nCommand finished successfully!"));
196
- if (!isPinnedExecution && !getPinnedCommands().includes(finalArgs.join(" "))) {
197
- setTimeout(async () => {
198
- const shouldPin = await p.confirm({
199
- message: pc.magenta("Pin this command for easy access next time?"),
200
- initialValue: false
201
- });
202
- if (shouldPin && !p.isCancel(shouldPin)) {
203
- addPinnedCommand(finalArgs.join(" "));
204
- p.log.success("Pinned! You can find it at the top of the main menu.");
205
- }
206
- }, 100);
645
+ }
646
+ }
647
+ nextItems.push({
648
+ id: "section-all-commands",
649
+ value: "__section_all_commands__",
650
+ label: "\u{1F4C2} All Commands",
651
+ kind: "header",
652
+ selectable: false
653
+ });
654
+ for (const [categoryKey, category] of Object.entries(categories)) {
655
+ nextItems.push({
656
+ id: `category-${categoryKey}`,
657
+ value: `__category_${categoryKey}__`,
658
+ label: `${category.icon} ${category.label}`,
659
+ kind: "header",
660
+ selectable: false
661
+ });
662
+ for (const command of category.commands) {
663
+ const pinHint = pinnedCommandSet.has(command.value) ? "pinned" : void 0;
664
+ nextItems.push({
665
+ id: `cmd-${categoryKey}-${command.value}`,
666
+ value: command.value,
667
+ label: command.label,
668
+ hint: [command.hint, pinHint].filter(Boolean).join(" \xB7 "),
669
+ kind: "command",
670
+ rightActionable: true,
671
+ section: categoryKey,
672
+ groupLabel: category.label
673
+ });
674
+ }
675
+ }
676
+ nextItems.push({
677
+ id: "section-actions",
678
+ value: "__section_actions__",
679
+ label: "\u26A1 Actions",
680
+ kind: "header",
681
+ selectable: false
682
+ });
683
+ nextItems.push({
684
+ id: "action-custom",
685
+ value: "__action_custom__",
686
+ label: "Custom Command",
687
+ hint: "Free-form args or check version",
688
+ icon: "\u270F\uFE0F",
689
+ kind: "action"
690
+ });
691
+ nextItems.push({
692
+ id: "action-exit",
693
+ value: "__action_exit__",
694
+ label: "Exit",
695
+ icon: "\u{1F6AA}",
696
+ kind: "action"
697
+ });
698
+ return nextItems;
699
+ }
700
+
701
+ // src/screens/MainMenu.tsx
702
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
703
+ function MainMenu({
704
+ onNavigate,
705
+ onExit
706
+ }) {
707
+ const [pinnedCommands, setPinnedCommands2] = useState3(
708
+ () => getPinnedCommands()
709
+ );
710
+ const [pinnedRuns, setPinnedRuns2] = useState3(
711
+ () => getPinnedRuns()
712
+ );
713
+ const [pinFeedback, setPinFeedback] = useState3();
714
+ useEffect2(() => {
715
+ if (!pinFeedback) return;
716
+ const timeout = setTimeout(() => setPinFeedback(void 0), 1400);
717
+ return () => clearTimeout(timeout);
718
+ }, [pinFeedback]);
719
+ const items = useMemo2(
720
+ () => buildMainMenuItems({
721
+ pinnedCommands,
722
+ pinnedRuns
723
+ }),
724
+ [pinnedCommands, pinnedRuns]
725
+ );
726
+ const pinnedCommandSet = useMemo2(
727
+ () => new Set(pinnedCommands),
728
+ [pinnedCommands]
729
+ );
730
+ const pinnedRunSet = useMemo2(() => new Set(pinnedRuns), [pinnedRuns]);
731
+ const refreshPins = () => {
732
+ setPinnedCommands2(getPinnedCommands());
733
+ setPinnedRuns2(getPinnedRuns());
734
+ };
735
+ const handleSelect = (value, item) => {
736
+ if (!item) return;
737
+ if (item.kind === "command") {
738
+ onNavigate("command-args", { command: value });
739
+ return;
740
+ }
741
+ if (item.kind === "run") {
742
+ const args = value.split(" ").filter(Boolean);
743
+ if (args.length > 0) {
744
+ onNavigate("confirm-execute", { args });
745
+ }
746
+ return;
747
+ }
748
+ if (value === "__action_custom__") {
749
+ onNavigate("custom-command");
750
+ return;
751
+ }
752
+ if (value === "__action_exit__") {
753
+ onExit();
754
+ }
755
+ };
756
+ const handleRightAction = (item) => {
757
+ if (item.kind === "command") {
758
+ const command = item.value;
759
+ const wasPinned = pinnedCommandSet.has(command);
760
+ togglePinnedCommand(command);
761
+ refreshPins();
762
+ setPinFeedback(
763
+ wasPinned ? `Unpinned "${command}"` : `Pinned "${command}"`
764
+ );
765
+ return;
766
+ }
767
+ if (item.kind === "run") {
768
+ const runCommand = item.value;
769
+ const wasPinned = pinnedRunSet.has(runCommand);
770
+ togglePinnedRun(runCommand);
771
+ refreshPins();
772
+ setPinFeedback(
773
+ wasPinned ? `Unpinned exact run "${runCommand}"` : `Pinned exact run "${runCommand}"`
774
+ );
775
+ }
776
+ };
777
+ return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
778
+ /* @__PURE__ */ jsx4(GhostBanner, {}),
779
+ /* @__PURE__ */ jsx4(Box4, { marginBottom: 1, children: /* @__PURE__ */ jsx4(Text4, { bold: true, children: "Choose a command, then press Enter to continue." }) }),
780
+ pinFeedback && /* @__PURE__ */ jsx4(Box4, { marginBottom: 1, children: /* @__PURE__ */ jsxs4(Text4, { color: "green", children: [
781
+ "\u2713 ",
782
+ pinFeedback
783
+ ] }) }),
784
+ /* @__PURE__ */ jsx4(
785
+ SelectList,
786
+ {
787
+ items,
788
+ onSelect: handleSelect,
789
+ onRightAction: handleRightAction,
790
+ boxedSections: true
791
+ }
792
+ ),
793
+ /* @__PURE__ */ jsx4(StatusBar, { hint: "\u2191\u2193 navigate \xB7 Enter select \xB7 \u2192 pin/unpin \xB7 Esc back" })
794
+ ] });
795
+ }
796
+
797
+ // src/screens/CommandArgs.tsx
798
+ import { useEffect as useEffect3, useMemo as useMemo3, useState as useState5 } from "react";
799
+ import { Box as Box6, Text as Text6 } from "ink";
800
+
801
+ // src/components/TextPrompt.tsx
802
+ import { useState as useState4 } from "react";
803
+ import { Box as Box5, Text as Text5, useInput as useInput2 } from "ink";
804
+ import TextInputComponent from "ink-text-input";
805
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
806
+ function TextPrompt({
807
+ label,
808
+ placeholder,
809
+ onSubmit,
810
+ onCancel,
811
+ validate
812
+ }) {
813
+ const [value, setValue] = useState4("");
814
+ const [error, setError] = useState4();
815
+ useInput2((_input, key) => {
816
+ if (key.escape && onCancel) {
817
+ onCancel();
818
+ }
819
+ });
820
+ const handleSubmit = (val) => {
821
+ if (validate) {
822
+ const err = validate(val);
823
+ if (err) {
824
+ setError(err);
825
+ return;
826
+ }
827
+ }
828
+ setError(void 0);
829
+ onSubmit(val);
830
+ };
831
+ return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
832
+ /* @__PURE__ */ jsxs5(Box5, { gap: 1, children: [
833
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", bold: true, children: "?" }),
834
+ /* @__PURE__ */ jsx5(Text5, { children: label })
835
+ ] }),
836
+ /* @__PURE__ */ jsxs5(Box5, { gap: 1, marginLeft: 2, children: [
837
+ /* @__PURE__ */ jsx5(Text5, { color: "cyan", children: "\u276F" }),
838
+ /* @__PURE__ */ jsx5(
839
+ TextInputComponent,
840
+ {
841
+ value,
842
+ onChange: (val) => {
843
+ setValue(val);
844
+ setError(void 0);
845
+ },
846
+ onSubmit: handleSubmit,
847
+ placeholder
848
+ }
849
+ )
850
+ ] }),
851
+ error && /* @__PURE__ */ jsx5(Box5, { marginLeft: 2, children: /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
852
+ "\u2717 ",
853
+ error
854
+ ] }) })
855
+ ] });
856
+ }
857
+
858
+ // src/data/suggestedArgs.ts
859
+ var suggestedArgsByCommand = {
860
+ db: [
861
+ { value: "pull", label: "pull", hint: "Pull schema from remote", args: ["pull"] },
862
+ { value: "push", label: "push", hint: "Push local migrations", args: ["push"] },
863
+ { value: "reset", label: "reset", hint: "Reset local database", args: ["reset"] },
864
+ { value: "dump", label: "dump", hint: "Dump schema/data", args: ["dump"] },
865
+ { value: "diff", label: "diff", hint: "Show migration diff", args: ["diff"] },
866
+ { value: "lint", label: "lint", hint: "Lint SQL migrations", args: ["lint"] }
867
+ ],
868
+ migration: [
869
+ { value: "new", label: "new <name>", hint: "Create migration file", args: ["new"] },
870
+ { value: "up", label: "up", hint: "Apply pending migrations", args: ["up"] },
871
+ { value: "down", label: "down", hint: "Rollback last migration", args: ["down"] },
872
+ { value: "list", label: "list", hint: "List local migrations", args: ["list"] },
873
+ { value: "repair", label: "repair", hint: "Repair migration history", args: ["repair"] },
874
+ { value: "fetch", label: "fetch", hint: "Fetch migration history", args: ["fetch"] }
875
+ ],
876
+ projects: [
877
+ { value: "list", label: "list", hint: "List projects", args: ["list"] },
878
+ { value: "create", label: "create", hint: "Create project", args: ["create"] },
879
+ { value: "delete", label: "delete", hint: "Delete project", args: ["delete"] }
880
+ ],
881
+ functions: [
882
+ { value: "list", label: "list", hint: "List functions", args: ["list"] },
883
+ { value: "new", label: "new <name>", hint: "Create a function", args: ["new"] },
884
+ { value: "serve", label: "serve", hint: "Serve functions locally", args: ["serve"] },
885
+ { value: "deploy", label: "deploy <name>", hint: "Deploy function", args: ["deploy"] },
886
+ { value: "delete", label: "delete <name>", hint: "Delete function", args: ["delete"] }
887
+ ],
888
+ storage: [
889
+ { value: "ls", label: "ls", hint: "List storage objects", args: ["ls"] },
890
+ { value: "cp", label: "cp", hint: "Copy storage object", args: ["cp"] },
891
+ { value: "mv", label: "mv", hint: "Move storage object", args: ["mv"] },
892
+ { value: "rm", label: "rm", hint: "Remove storage object", args: ["rm"] }
893
+ ],
894
+ seed: [
895
+ { value: "run", label: "run", hint: "Run configured seed file", args: ["run"] }
896
+ ],
897
+ orgs: [
898
+ { value: "list", label: "list", hint: "List organizations", args: ["list"] },
899
+ { value: "create", label: "create", hint: "Create organization", args: ["create"] }
900
+ ],
901
+ snippets: [
902
+ { value: "list", label: "list", hint: "List SQL snippets", args: ["list"] },
903
+ { value: "create", label: "create", hint: "Create SQL snippet", args: ["create"] },
904
+ { value: "delete", label: "delete", hint: "Delete SQL snippet", args: ["delete"] }
905
+ ],
906
+ backups: [
907
+ { value: "list", label: "list", hint: "List backups", args: ["list"] },
908
+ { value: "download", label: "download", hint: "Download backup", args: ["download"] }
909
+ ]
910
+ };
911
+ function getSuggestedArgOptions(command) {
912
+ return suggestedArgsByCommand[command] ?? [];
913
+ }
914
+
915
+ // src/screens/commandArgsModel.ts
916
+ function buildRunCommand(command, extraArgs) {
917
+ return [command, ...extraArgs].join(" ");
918
+ }
919
+ function getRunCommandFromArgsSelection(command, value, suggestions) {
920
+ if (value === "__run_base__") {
921
+ return command;
922
+ }
923
+ if (!value.startsWith("suggest:")) {
924
+ return void 0;
925
+ }
926
+ const optionKey = value.replace("suggest:", "");
927
+ const option = suggestions.find((entry) => entry.value === optionKey);
928
+ if (!option) {
929
+ return void 0;
930
+ }
931
+ return buildRunCommand(command, option.args);
932
+ }
933
+ function buildCommandArgItems({
934
+ command,
935
+ suggestions,
936
+ pinnedRuns
937
+ }) {
938
+ const pinnedRunSet = new Set(pinnedRuns);
939
+ const baseRunCommand = buildRunCommand(command, []);
940
+ return [
941
+ {
942
+ value: "__suggested_header__",
943
+ label: `Suggested args for ${command}`,
944
+ kind: "header",
945
+ selectable: false
946
+ },
947
+ ...suggestions.map((option) => {
948
+ const runCommand = buildRunCommand(command, option.args);
949
+ const pinHint = pinnedRunSet.has(runCommand) ? "pinned run" : void 0;
950
+ return {
951
+ value: `suggest:${option.value}`,
952
+ label: option.label,
953
+ hint: [option.hint, pinHint].filter(Boolean).join(" \xB7 "),
954
+ kind: "command",
955
+ rightActionable: true
956
+ };
957
+ }),
958
+ {
959
+ value: "__actions_header__",
960
+ label: "\u26A1 Actions",
961
+ kind: "header",
962
+ selectable: false
963
+ },
964
+ {
965
+ value: "__run_base__",
966
+ label: "Run without additional args",
967
+ hint: [
968
+ `supabase ${command}`,
969
+ pinnedRunSet.has(baseRunCommand) ? "pinned run" : void 0
970
+ ].filter(Boolean).join(" \xB7 "),
971
+ kind: "action",
972
+ rightActionable: true
973
+ },
974
+ {
975
+ value: "__custom__",
976
+ label: "Custom args...",
977
+ hint: "Type any arguments manually",
978
+ kind: "action"
979
+ },
980
+ {
981
+ value: "__back__",
982
+ label: "\u2190 Back to menu",
983
+ kind: "action"
984
+ }
985
+ ];
986
+ }
987
+
988
+ // src/screens/CommandArgs.tsx
989
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
990
+ function CommandArgs({
991
+ command,
992
+ onNavigate,
993
+ onBack
994
+ }) {
995
+ const suggestions = useMemo3(() => getSuggestedArgOptions(command), [command]);
996
+ const [pinnedRuns, setPinnedRuns2] = useState5(() => getPinnedRuns());
997
+ const [pinFeedback, setPinFeedback] = useState5();
998
+ const [phase, setPhase] = useState5(
999
+ suggestions.length > 0 ? "select" : "custom"
1000
+ );
1001
+ useEffect3(() => {
1002
+ setPhase(suggestions.length > 0 ? "select" : "custom");
1003
+ }, [command, suggestions.length]);
1004
+ useEffect3(() => {
1005
+ if (!pinFeedback) return;
1006
+ const timeout = setTimeout(() => setPinFeedback(void 0), 1400);
1007
+ return () => clearTimeout(timeout);
1008
+ }, [pinFeedback]);
1009
+ const navigateWithExtraArgs = (extraArgs) => {
1010
+ onNavigate("flag-selection", {
1011
+ args: [command, ...extraArgs],
1012
+ command
1013
+ });
1014
+ };
1015
+ if (!command) {
1016
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1017
+ /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "red", children: "Command not provided." }) }),
1018
+ /* @__PURE__ */ jsx6(
1019
+ SelectList,
1020
+ {
1021
+ items: [{ value: "__back__", label: "\u2190 Back to menu" }],
1022
+ onSelect: onBack,
1023
+ onCancel: onBack
1024
+ }
1025
+ )
1026
+ ] });
1027
+ }
1028
+ if (phase === "custom") {
1029
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1030
+ /* @__PURE__ */ jsxs6(Box6, { marginBottom: 1, gap: 1, children: [
1031
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", bold: true, children: "Command" }),
1032
+ /* @__PURE__ */ jsx6(Text6, { children: command })
1033
+ ] }),
1034
+ /* @__PURE__ */ jsx6(
1035
+ TextPrompt,
1036
+ {
1037
+ label: `Additional args for supabase ${command}?`,
1038
+ placeholder: "e.g. list, pull, push (Enter to skip)",
1039
+ onSubmit: (extra) => {
1040
+ const extraArgs = extra.trim().split(" ").filter(Boolean);
1041
+ navigateWithExtraArgs(extraArgs);
1042
+ },
1043
+ onCancel: () => {
1044
+ if (suggestions.length > 0) {
1045
+ setPhase("select");
1046
+ return;
1047
+ }
1048
+ onBack();
1049
+ }
1050
+ }
1051
+ ),
1052
+ /* @__PURE__ */ jsx6(StatusBar, { hint: "Type args \xB7 Enter to continue \xB7 Esc to go back" })
1053
+ ] });
1054
+ }
1055
+ const items = useMemo3(
1056
+ () => buildCommandArgItems({
1057
+ command,
1058
+ suggestions,
1059
+ pinnedRuns
1060
+ }),
1061
+ [command, pinnedRuns, suggestions]
1062
+ );
1063
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1064
+ /* @__PURE__ */ jsxs6(Box6, { marginBottom: 1, gap: 1, children: [
1065
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", bold: true, children: "Command" }),
1066
+ /* @__PURE__ */ jsx6(Text6, { children: command })
1067
+ ] }),
1068
+ pinFeedback && /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "green", children: [
1069
+ "\u2713 ",
1070
+ pinFeedback
1071
+ ] }) }),
1072
+ /* @__PURE__ */ jsx6(
1073
+ SelectList,
1074
+ {
1075
+ items,
1076
+ onSelect: (value) => {
1077
+ if (value === "__back__") {
1078
+ onBack();
1079
+ return;
1080
+ }
1081
+ if (value === "__custom__") {
1082
+ setPhase("custom");
1083
+ return;
207
1084
  }
1085
+ if (value === "__run_base__") {
1086
+ navigateWithExtraArgs([]);
1087
+ return;
1088
+ }
1089
+ if (value.startsWith("suggest:")) {
1090
+ const runCommand = getRunCommandFromArgsSelection(
1091
+ command,
1092
+ value,
1093
+ suggestions
1094
+ );
1095
+ if (runCommand) {
1096
+ navigateWithExtraArgs(
1097
+ runCommand.split(" ").slice(1).filter(Boolean)
1098
+ );
1099
+ }
1100
+ return;
1101
+ }
1102
+ },
1103
+ onRightAction: (item) => {
1104
+ const runCommand = getRunCommandFromArgsSelection(
1105
+ command,
1106
+ item.value,
1107
+ suggestions
1108
+ );
1109
+ if (!runCommand) {
1110
+ return;
1111
+ }
1112
+ const wasPinned = pinnedRuns.includes(runCommand);
1113
+ togglePinnedRun(runCommand);
1114
+ setPinnedRuns2(getPinnedRuns());
1115
+ setPinFeedback(
1116
+ wasPinned ? `Unpinned exact run "${runCommand}"` : `Pinned exact run "${runCommand}"`
1117
+ );
1118
+ },
1119
+ onCancel: onBack,
1120
+ boxedSections: true
1121
+ }
1122
+ ),
1123
+ /* @__PURE__ */ jsx6(StatusBar, { hint: "\u2191\u2193 navigate \xB7 Enter select \xB7 \u2192 pin run \xB7 Esc back" })
1124
+ ] });
1125
+ }
1126
+
1127
+ // src/screens/CustomCommand.tsx
1128
+ import { Box as Box7 } from "ink";
1129
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1130
+ function CustomCommand({
1131
+ onNavigate,
1132
+ onBack
1133
+ }) {
1134
+ return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
1135
+ /* @__PURE__ */ jsx7(
1136
+ TextPrompt,
1137
+ {
1138
+ label: "Enter your Supabase command/flags:",
1139
+ placeholder: "e.g. -v, status -o json, db pull",
1140
+ validate: (val) => {
1141
+ if (!val || !val.trim()) return "Please enter a command";
1142
+ return void 0;
1143
+ },
1144
+ onSubmit: (value) => {
1145
+ const args = value.split(" ").filter(Boolean);
1146
+ onNavigate("flag-selection", { args });
1147
+ },
1148
+ onCancel: onBack
1149
+ }
1150
+ ),
1151
+ /* @__PURE__ */ jsx7(StatusBar, { hint: "Type a command \xB7 Enter to continue \xB7 Esc to go back" })
1152
+ ] });
1153
+ }
1154
+
1155
+ // src/screens/FlagSelection.tsx
1156
+ import { Box as Box9 } from "ink";
1157
+
1158
+ // src/components/FlagToggle.tsx
1159
+ import { useState as useState6 } from "react";
1160
+ import { Box as Box8, Text as Text7, useInput as useInput3 } from "ink";
1161
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1162
+ function FlagToggle({
1163
+ flags,
1164
+ onSubmit,
1165
+ onCancel
1166
+ }) {
1167
+ const [cursor, setCursor] = useState6(0);
1168
+ const [selected, setSelected] = useState6(/* @__PURE__ */ new Set());
1169
+ useInput3((input, key) => {
1170
+ if (key.upArrow || input === "k") {
1171
+ setCursor((prev) => prev > 0 ? prev - 1 : flags.length - 1);
1172
+ }
1173
+ if (key.downArrow || input === "j") {
1174
+ setCursor((prev) => prev < flags.length - 1 ? prev + 1 : 0);
1175
+ }
1176
+ if (input === " ") {
1177
+ setSelected((prev) => {
1178
+ const next = new Set(prev);
1179
+ const flag = flags[cursor];
1180
+ if (next.has(flag.value)) {
1181
+ next.delete(flag.value);
1182
+ } else {
1183
+ next.add(flag.value);
208
1184
  }
209
- resolve();
1185
+ return next;
1186
+ });
1187
+ }
1188
+ if (key.return) {
1189
+ onSubmit(Array.from(selected));
1190
+ }
1191
+ if (key.escape && onCancel) {
1192
+ onCancel();
1193
+ }
1194
+ });
1195
+ return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
1196
+ /* @__PURE__ */ jsxs8(Box8, { marginBottom: 1, children: [
1197
+ /* @__PURE__ */ jsx8(Text7, { bold: true, color: "cyan", children: "\u2691 Global Flags" }),
1198
+ /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: " (Space to toggle, Enter to confirm)" })
1199
+ ] }),
1200
+ flags.map((flag, i) => {
1201
+ const isActive = cursor === i;
1202
+ const isChecked = selected.has(flag.value);
1203
+ return /* @__PURE__ */ jsxs8(Box8, { gap: 1, children: [
1204
+ /* @__PURE__ */ jsx8(Text7, { color: isActive ? "cyan" : void 0, children: isActive ? "\u276F" : " " }),
1205
+ /* @__PURE__ */ jsx8(
1206
+ Text7,
1207
+ {
1208
+ color: isChecked ? "green" : isActive ? "cyan" : void 0,
1209
+ bold: isChecked,
1210
+ children: isChecked ? "\u25C9" : "\u25CB"
1211
+ }
1212
+ ),
1213
+ /* @__PURE__ */ jsx8(
1214
+ Text7,
1215
+ {
1216
+ color: isActive ? "cyan" : void 0,
1217
+ bold: isActive,
1218
+ children: flag.label
1219
+ }
1220
+ ),
1221
+ flag.hint && /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: flag.hint })
1222
+ ] }, flag.value);
1223
+ }),
1224
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx8(Text7, { dimColor: true, children: selected.size > 0 ? `${selected.size} flag${selected.size > 1 ? "s" : ""} selected` : "No flags selected (Enter to skip)" }) })
1225
+ ] });
1226
+ }
1227
+
1228
+ // src/data/flags.ts
1229
+ var globalFlags = [
1230
+ { value: "--debug", label: "--debug", hint: "Output debug logs to stderr" },
1231
+ { value: "--yes", label: "--yes", hint: "Answer yes to all prompts" },
1232
+ {
1233
+ value: "--create-ticket",
1234
+ label: "--create-ticket",
1235
+ hint: "Create support ticket on error"
1236
+ },
1237
+ {
1238
+ value: "--experimental",
1239
+ label: "--experimental",
1240
+ hint: "Enable experimental features"
1241
+ }
1242
+ ];
1243
+
1244
+ // src/screens/FlagSelection.tsx
1245
+ import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1246
+ function FlagSelection({
1247
+ args,
1248
+ onNavigate,
1249
+ onBack
1250
+ }) {
1251
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
1252
+ /* @__PURE__ */ jsx9(
1253
+ FlagToggle,
1254
+ {
1255
+ flags: globalFlags,
1256
+ onSubmit: (selectedFlags) => {
1257
+ const finalArgs = selectedFlags.length > 0 ? [...args, ...selectedFlags] : args;
1258
+ onNavigate("confirm-execute", { args: finalArgs });
1259
+ },
1260
+ onCancel: onBack
1261
+ }
1262
+ ),
1263
+ /* @__PURE__ */ jsx9(StatusBar, { hint: "Space toggle \xB7 Enter confirm \xB7 Esc back" })
1264
+ ] });
1265
+ }
1266
+
1267
+ // src/screens/CommandExecution.tsx
1268
+ import { useState as useState8, useEffect as useEffect4 } from "react";
1269
+ import { Box as Box12, Text as Text11 } from "ink";
1270
+
1271
+ // src/components/Spinner.tsx
1272
+ import { Text as Text8 } from "ink";
1273
+ import InkSpinner from "ink-spinner";
1274
+ import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1275
+ function Spinner({
1276
+ label = "Running...",
1277
+ color = "cyan"
1278
+ }) {
1279
+ return /* @__PURE__ */ jsxs10(Text8, { children: [
1280
+ /* @__PURE__ */ jsx10(Text8, { color, children: /* @__PURE__ */ jsx10(InkSpinner, { type: "dots" }) }),
1281
+ " ",
1282
+ /* @__PURE__ */ jsx10(Text8, { children: label })
1283
+ ] });
1284
+ }
1285
+
1286
+ // src/components/ConfirmPrompt.tsx
1287
+ import { Box as Box10, Text as Text9, useInput as useInput4 } from "ink";
1288
+ import { jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1289
+ function ConfirmPrompt({
1290
+ message,
1291
+ defaultValue = true,
1292
+ onConfirm
1293
+ }) {
1294
+ useInput4((input) => {
1295
+ if (input === "y" || input === "Y") {
1296
+ onConfirm(true);
1297
+ } else if (input === "n" || input === "N") {
1298
+ onConfirm(false);
1299
+ } else if (input === "\r") {
1300
+ onConfirm(defaultValue);
1301
+ }
1302
+ });
1303
+ return /* @__PURE__ */ jsxs11(Box10, { gap: 1, children: [
1304
+ /* @__PURE__ */ jsx11(Text9, { color: "cyan", bold: true, children: "?" }),
1305
+ /* @__PURE__ */ jsx11(Text9, { children: message }),
1306
+ /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: defaultValue ? "(Y/n)" : "(y/N)" })
1307
+ ] });
1308
+ }
1309
+
1310
+ // src/components/Divider.tsx
1311
+ import { Text as Text10 } from "ink";
1312
+ import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1313
+ function Divider({
1314
+ label,
1315
+ width = 50
1316
+ }) {
1317
+ if (label) {
1318
+ const labelLen = label.length + 2;
1319
+ const sideLen = Math.max(2, Math.floor((width - labelLen) / 2));
1320
+ const left = "\u2500".repeat(sideLen);
1321
+ const right = "\u2500".repeat(sideLen);
1322
+ return /* @__PURE__ */ jsxs12(Text10, { dimColor: true, children: [
1323
+ left,
1324
+ " ",
1325
+ label,
1326
+ " ",
1327
+ right
1328
+ ] });
1329
+ }
1330
+ return /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "\u2500".repeat(width) });
1331
+ }
1332
+
1333
+ // src/hooks/useCommand.ts
1334
+ import { useState as useState7, useCallback as useCallback2 } from "react";
1335
+
1336
+ // src/lib/runner.ts
1337
+ import { spawn } from "child_process";
1338
+ async function runSupabaseCommand(args) {
1339
+ return new Promise((resolve) => {
1340
+ let stdout = "";
1341
+ let stderr = "";
1342
+ const child = spawn("supabase", args, {
1343
+ shell: true,
1344
+ stdio: ["inherit", "pipe", "pipe"]
1345
+ });
1346
+ child.stdout?.on("data", (data) => {
1347
+ const text = data.toString();
1348
+ stdout += text;
1349
+ process.stdout.write(text);
1350
+ });
1351
+ child.stderr?.on("data", (data) => {
1352
+ const text = data.toString();
1353
+ stderr += text;
1354
+ process.stderr.write(text);
1355
+ });
1356
+ child.on("error", (err) => {
1357
+ resolve({
1358
+ exitCode: null,
1359
+ signal: null,
1360
+ stdout,
1361
+ stderr,
1362
+ spawnError: err.message
210
1363
  });
211
1364
  });
212
- const next = await p.confirm({
213
- message: "Do you want to run another command?",
214
- initialValue: true
1365
+ child.on("exit", (code, signal) => {
1366
+ resolve({ exitCode: code, signal, stdout, stderr });
215
1367
  });
216
- if (!next || p.isCancel(next)) {
217
- shouldExit = true;
1368
+ });
1369
+ }
1370
+
1371
+ // src/hooks/useCommand.ts
1372
+ function useCommand() {
1373
+ const [status, setStatus] = useState7("idle");
1374
+ const [result, setResult] = useState7(null);
1375
+ const run = useCallback2(async (args) => {
1376
+ setStatus("running");
1377
+ setResult(null);
1378
+ const res = await runSupabaseCommand(args);
1379
+ setResult(res);
1380
+ if (res.spawnError || res.exitCode !== null && res.exitCode !== 0) {
1381
+ setStatus("error");
218
1382
  } else {
219
- console.clear();
220
- p.intro(`${pc.bgGreen(pc.black(" POLTERBASE "))} Continue managing your Supabase project`);
1383
+ setStatus("success");
1384
+ }
1385
+ return res;
1386
+ }, []);
1387
+ const reset = useCallback2(() => {
1388
+ setStatus("idle");
1389
+ setResult(null);
1390
+ }, []);
1391
+ return { status, result, run, reset };
1392
+ }
1393
+
1394
+ // src/lib/clipboard.ts
1395
+ import { spawn as spawn2, exec } from "child_process";
1396
+ async function openInBrowser(url) {
1397
+ return new Promise((resolve) => {
1398
+ const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
1399
+ exec(cmd, () => resolve());
1400
+ });
1401
+ }
1402
+ async function copyToClipboard(text) {
1403
+ return new Promise((resolve) => {
1404
+ const cmd = process.platform === "darwin" ? "pbcopy" : process.platform === "win32" ? "clip" : "xclip -selection clipboard";
1405
+ const child = spawn2(cmd, [], { shell: true });
1406
+ child.stdin?.write(text);
1407
+ child.stdin?.end();
1408
+ child.on("exit", () => resolve());
1409
+ child.on("error", () => resolve());
1410
+ });
1411
+ }
1412
+
1413
+ // src/screens/CommandExecution.tsx
1414
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1415
+ function CommandExecution({
1416
+ args: initialArgs,
1417
+ onBack,
1418
+ onExit
1419
+ }) {
1420
+ const [phase, setPhase] = useState8("confirm");
1421
+ const [currentArgs, setCurrentArgs] = useState8(initialArgs);
1422
+ const [pinMessage, setPinMessage] = useState8();
1423
+ const { status, result, run, reset } = useCommand();
1424
+ const cmdDisplay = `supabase ${currentArgs.join(" ")}`;
1425
+ const runCommand = currentArgs.join(" ");
1426
+ useEffect4(() => {
1427
+ if (phase === "running" && status === "idle") {
1428
+ run(currentArgs);
221
1429
  }
1430
+ }, [phase, status, run, currentArgs]);
1431
+ useEffect4(() => {
1432
+ if (phase === "running" && status === "success") {
1433
+ if (isPinnedRun(runCommand)) {
1434
+ setPhase("success");
1435
+ } else {
1436
+ setPhase("success-pin-offer");
1437
+ }
1438
+ }
1439
+ if (phase === "running" && status === "error") {
1440
+ setPhase("error-menu");
1441
+ }
1442
+ }, [phase, runCommand, status]);
1443
+ if (phase === "confirm") {
1444
+ return /* @__PURE__ */ jsx13(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsx13(
1445
+ ConfirmPrompt,
1446
+ {
1447
+ message: `Execute ${cmdDisplay}?`,
1448
+ defaultValue: true,
1449
+ onConfirm: (confirmed) => {
1450
+ if (confirmed) {
1451
+ setPhase("running");
1452
+ } else {
1453
+ onBack();
1454
+ }
1455
+ }
1456
+ }
1457
+ ) });
1458
+ }
1459
+ if (phase === "running") {
1460
+ return /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", children: [
1461
+ /* @__PURE__ */ jsx13(Divider, {}),
1462
+ /* @__PURE__ */ jsxs13(Box12, { marginY: 1, gap: 1, children: [
1463
+ /* @__PURE__ */ jsx13(Text11, { color: "cyan", bold: true, children: "\u25B6" }),
1464
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Running:" }),
1465
+ /* @__PURE__ */ jsx13(Text11, { children: cmdDisplay })
1466
+ ] }),
1467
+ /* @__PURE__ */ jsx13(Divider, {}),
1468
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx13(Spinner, { label: `Executing ${cmdDisplay}...` }) })
1469
+ ] });
1470
+ }
1471
+ if (phase === "success-pin-offer") {
1472
+ return /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", children: [
1473
+ /* @__PURE__ */ jsx13(Divider, {}),
1474
+ /* @__PURE__ */ jsxs13(Box12, { marginY: 1, gap: 1, children: [
1475
+ /* @__PURE__ */ jsx13(Text11, { color: "green", bold: true, children: "\u2713" }),
1476
+ /* @__PURE__ */ jsx13(Text11, { color: "green", bold: true, children: "Command completed successfully!" })
1477
+ ] }),
1478
+ /* @__PURE__ */ jsx13(
1479
+ ConfirmPrompt,
1480
+ {
1481
+ message: "Pin this exact command?",
1482
+ defaultValue: false,
1483
+ onConfirm: (shouldPin) => {
1484
+ if (shouldPin && !isPinnedRun(runCommand)) {
1485
+ togglePinnedRun(runCommand);
1486
+ setPinMessage("Exact command pinned to Pinned Runs.");
1487
+ }
1488
+ setPhase("success");
1489
+ }
1490
+ }
1491
+ )
1492
+ ] });
1493
+ }
1494
+ if (phase === "success") {
1495
+ return /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", children: [
1496
+ /* @__PURE__ */ jsx13(Divider, {}),
1497
+ /* @__PURE__ */ jsxs13(Box12, { marginY: 1, gap: 1, children: [
1498
+ /* @__PURE__ */ jsx13(Text11, { color: "green", bold: true, children: "\u2713" }),
1499
+ /* @__PURE__ */ jsx13(Text11, { color: "green", bold: true, children: "Command completed successfully!" })
1500
+ ] }),
1501
+ pinMessage && /* @__PURE__ */ jsx13(Box12, { marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { color: "green", children: pinMessage }) }),
1502
+ /* @__PURE__ */ jsx13(
1503
+ SelectList,
1504
+ {
1505
+ items: [{ value: "__back__", label: "\u2190 Back to menu" }],
1506
+ onSelect: onBack,
1507
+ onCancel: onBack
1508
+ }
1509
+ )
1510
+ ] });
1511
+ }
1512
+ const hasDebug = currentArgs.includes("--debug");
1513
+ const errorItems = [];
1514
+ if (!result?.spawnError) {
1515
+ errorItems.push({ value: "retry", label: "\u{1F504} Retry the same command" });
1516
+ if (!hasDebug) {
1517
+ errorItems.push({
1518
+ value: "retry-debug",
1519
+ label: "\u{1F41B} Retry with --debug",
1520
+ hint: "Append --debug for verbose logs"
1521
+ });
1522
+ }
1523
+ }
1524
+ errorItems.push(
1525
+ {
1526
+ value: "docs",
1527
+ label: "\u{1F4D6} Open Supabase CLI docs",
1528
+ hint: "Opens in browser"
1529
+ },
1530
+ {
1531
+ value: "copy",
1532
+ label: "\u{1F4CB} Copy command to clipboard"
1533
+ },
1534
+ { value: "menu", label: "\u2190 Return to main menu" },
1535
+ { value: "exit", label: "\u{1F6AA} Exit Polterbase" }
1536
+ );
1537
+ return /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", children: [
1538
+ /* @__PURE__ */ jsx13(Divider, {}),
1539
+ result?.spawnError ? /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", marginY: 1, children: [
1540
+ /* @__PURE__ */ jsxs13(Box12, { gap: 1, children: [
1541
+ /* @__PURE__ */ jsx13(Text11, { color: "red", bold: true, children: "\u2717" }),
1542
+ /* @__PURE__ */ jsx13(Text11, { color: "red", bold: true, children: "Failed to start command" })
1543
+ ] }),
1544
+ /* @__PURE__ */ jsxs13(Box12, { marginLeft: 2, marginTop: 1, children: [
1545
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Error: " }),
1546
+ /* @__PURE__ */ jsx13(Text11, { color: "red", children: result.spawnError })
1547
+ ] }),
1548
+ (result.spawnError.includes("ENOENT") || result.spawnError.includes("not found")) && /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [
1549
+ /* @__PURE__ */ jsx13(Text11, { color: "yellow", bold: true, children: "\u{1F4A1} Supabase CLI not found in PATH" }),
1550
+ /* @__PURE__ */ jsxs13(Box12, { gap: 1, children: [
1551
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Install it:" }),
1552
+ /* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "https://supabase.com/docs/guides/cli" })
1553
+ ] })
1554
+ ] })
1555
+ ] }) : /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", marginY: 1, children: [
1556
+ /* @__PURE__ */ jsxs13(Box12, { gap: 1, children: [
1557
+ /* @__PURE__ */ jsx13(Text11, { color: "red", bold: true, children: "\u2717" }),
1558
+ /* @__PURE__ */ jsx13(Text11, { color: "red", children: "Command failed " }),
1559
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "(exit code " }),
1560
+ /* @__PURE__ */ jsx13(Text11, { color: "red", bold: true, children: String(result?.exitCode) }),
1561
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: ")" })
1562
+ ] }),
1563
+ /* @__PURE__ */ jsxs13(Box12, { marginLeft: 2, marginTop: 1, children: [
1564
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Command: " }),
1565
+ /* @__PURE__ */ jsx13(Text11, { children: cmdDisplay })
1566
+ ] }),
1567
+ !hasDebug && /* @__PURE__ */ jsxs13(Box12, { marginLeft: 2, marginTop: 1, gap: 1, children: [
1568
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "\u{1F4A1} Tip: retry with" }),
1569
+ /* @__PURE__ */ jsx13(Text11, { color: "cyan", children: "--debug" }),
1570
+ /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "to see detailed logs" })
1571
+ ] })
1572
+ ] }),
1573
+ /* @__PURE__ */ jsx13(Box12, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx13(Text11, { bold: true, children: "What would you like to do?" }) }),
1574
+ /* @__PURE__ */ jsx13(
1575
+ SelectList,
1576
+ {
1577
+ items: errorItems,
1578
+ onSelect: async (action) => {
1579
+ switch (action) {
1580
+ case "retry":
1581
+ setPinMessage(void 0);
1582
+ reset();
1583
+ setPhase("running");
1584
+ break;
1585
+ case "retry-debug": {
1586
+ const newArgs = [...currentArgs, "--debug"];
1587
+ setCurrentArgs(newArgs);
1588
+ setPinMessage(void 0);
1589
+ reset();
1590
+ setPhase("running");
1591
+ break;
1592
+ }
1593
+ case "docs":
1594
+ await openInBrowser("https://supabase.com/docs/guides/cli");
1595
+ break;
1596
+ case "copy":
1597
+ await copyToClipboard(cmdDisplay);
1598
+ break;
1599
+ case "menu":
1600
+ onBack();
1601
+ break;
1602
+ case "exit":
1603
+ onExit();
1604
+ break;
1605
+ }
1606
+ },
1607
+ onCancel: onBack
1608
+ }
1609
+ ),
1610
+ /* @__PURE__ */ jsx13(StatusBar, {})
1611
+ ] });
1612
+ }
1613
+
1614
+ // src/app.tsx
1615
+ import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1616
+ function App() {
1617
+ const { screen, params, navigate, goBack } = useNavigation();
1618
+ const { exit } = useApp();
1619
+ const handleExit = () => {
1620
+ process.stdout.write(
1621
+ "\n" + pc2.dim("Thank you for using ") + pc2.cyan(pc2.bold("Polterbase")) + pc2.dim("!") + "\n\n"
1622
+ );
1623
+ exit();
1624
+ };
1625
+ switch (screen) {
1626
+ case "main-menu":
1627
+ return /* @__PURE__ */ jsx14(MainMenu, { onNavigate: navigate, onExit: handleExit });
1628
+ case "command-args":
1629
+ return /* @__PURE__ */ jsx14(
1630
+ CommandArgs,
1631
+ {
1632
+ command: params.command ?? "",
1633
+ onNavigate: navigate,
1634
+ onBack: goBack
1635
+ }
1636
+ );
1637
+ case "custom-command":
1638
+ return /* @__PURE__ */ jsx14(CustomCommand, { onNavigate: navigate, onBack: goBack });
1639
+ case "flag-selection":
1640
+ return /* @__PURE__ */ jsx14(
1641
+ FlagSelection,
1642
+ {
1643
+ args: params.args ?? [],
1644
+ onNavigate: navigate,
1645
+ onBack: goBack
1646
+ }
1647
+ );
1648
+ case "confirm-execute":
1649
+ case "command-execution":
1650
+ return /* @__PURE__ */ jsx14(
1651
+ CommandExecution,
1652
+ {
1653
+ args: params.args ?? [],
1654
+ onBack: goBack,
1655
+ onExit: handleExit
1656
+ }
1657
+ );
1658
+ default:
1659
+ return /* @__PURE__ */ jsx14(Box13, { children: /* @__PURE__ */ jsxs14(Text12, { color: "red", children: [
1660
+ "Unknown screen: ",
1661
+ screen
1662
+ ] }) });
222
1663
  }
223
- p.outro("Thank you for using Polterbase!");
224
1664
  }
225
- main().catch(console.error);
1665
+
1666
+ // src/index.tsx
1667
+ render(React7.createElement(App));