@polterware/polterbase 0.2.4 → 0.2.5

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 +43 -12
  2. package/dist/index.js +361 -54
  3. package/package.json +2 -3
package/README.md CHANGED
@@ -18,17 +18,30 @@ Polterbase is a productivity layer on top of the official `supabase` CLI. Instea
18
18
  - **Global Flags Picker**: Add common global flags in one step
19
19
  - **Pinned Commands and Runs**: Toggle base command pins with `→` and pin exact runs after success
20
20
  - **Custom Command Mode**: Run raw Supabase arguments like `-v` or `status -o json`
21
- - **Shell Execution**: Executes your local `supabase` binary directly
21
+ - **Built-in Self-Update**: Update Polterbase for the current repository or globally through npm
22
+ - **Shell Execution**: Resolves `supabase` from the current repository first, then falls back to `PATH`
22
23
  - **TypeScript-based CLI**: Strongly typed internal implementation
23
24
 
24
25
  ---
25
26
 
26
27
  ## Installation
27
28
 
28
- ### Run without installing globally
29
+ ### Run one-off without installing
29
30
 
30
31
  ```bash
31
- npx @polterware/polterbase
32
+ npx @polterware/polterbase@latest
33
+ ```
34
+
35
+ ### Install in a repository
36
+
37
+ ```bash
38
+ npm install -D @polterware/polterbase
39
+ ```
40
+
41
+ Then run it from that repository with:
42
+
43
+ ```bash
44
+ npx polterbase
32
45
  ```
33
46
 
34
47
  ### Install globally
@@ -43,28 +56,36 @@ Then run:
43
56
  polterbase
44
57
  ```
45
58
 
46
- `polterbase` is a global CLI tool. Do not add it to `dependencies` or `devDependencies` of app projects.
59
+ Use a repository install when you want the CLI version pinned to one project.
60
+ Use a global install when you want the same CLI version across every repository.
47
61
 
48
62
  ### Update
49
63
 
50
- If you run Polterbase with `npx`, always use the latest published version explicitly:
64
+ If you installed it globally, update it with:
51
65
 
52
66
  ```bash
53
- npx @polterware/polterbase@latest
67
+ npm install -g @polterware/polterbase@latest
54
68
  ```
55
69
 
56
- If you installed it globally, update it with:
70
+ If you installed it in a repository, update it there with:
57
71
 
58
72
  ```bash
59
- npm install -g @polterware/polterbase@latest
73
+ npm install -D @polterware/polterbase@latest
60
74
  ```
61
75
 
76
+ You can also run the same update flow from inside Polterbase:
77
+
78
+ 1. Go to the `Actions` section
79
+ 2. Choose `Update Polterbase`
80
+ 3. Choose `Current repository` or `Global install`
81
+ 4. Confirm the npm update command
82
+
62
83
  ---
63
84
 
64
85
  ## Requirements
65
86
 
66
87
  - **Node.js**: `>= 18`
67
- - **Supabase CLI**: installed and available in `PATH`
88
+ - **Supabase CLI**: installed globally in `PATH` or locally in the current repository
68
89
 
69
90
  Check your environment:
70
91
 
@@ -83,12 +104,22 @@ Install Supabase CLI (official docs):
83
104
 
84
105
  ### Execution Model
85
106
 
86
- Polterbase always executes commands as:
107
+ Polterbase executes workflow commands as:
87
108
 
88
109
  ```bash
89
110
  supabase <command> <extra-args> <global-flags>
90
111
  ```
91
112
 
113
+ The self-update action is the only built-in exception and can run one of:
114
+
115
+ ```bash
116
+ npm install -g @polterware/polterbase@latest
117
+ ```
118
+
119
+ ```bash
120
+ npm install -D @polterware/polterbase@latest
121
+ ```
122
+
92
123
  ### Typical Flow
93
124
 
94
125
  1. Choose a command from the unified board
@@ -252,11 +283,11 @@ supabase db pull --yes
252
283
 
253
284
  ### `supabase: command not found`
254
285
 
255
- Supabase CLI is not installed or not in your `PATH`.
286
+ Supabase CLI is not installed in the current repository and is not available in your `PATH`.
256
287
 
257
288
  Fix:
258
289
 
259
- 1. Install Supabase CLI
290
+ 1. Install Supabase CLI globally or in the current repository
260
291
  2. Restart terminal
261
292
  3. Run `supabase --version`
262
293
 
package/dist/index.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.tsx
4
- import React7 from "react";
4
+ import React8 from "react";
5
5
  import { render } from "ink";
6
6
 
7
7
  // src/app.tsx
8
- import { Box as Box13, Text as Text12, useApp } from "ink";
8
+ import { Box as Box14, Text as Text13, useApp } from "ink";
9
9
 
10
10
  // src/hooks/useNavigation.ts
11
11
  import { useState, useCallback } from "react";
@@ -446,17 +446,17 @@ function getPinnedRuns() {
446
446
  function setPinnedRuns(runs) {
447
447
  config.set(RUN_PINS_KEY, runs);
448
448
  }
449
- function togglePinnedRun(runCommand) {
449
+ function togglePinnedRun(runCommand2) {
450
450
  ensurePinsInitialized();
451
451
  const current = getPinnedRuns();
452
- if (current.includes(runCommand)) {
453
- setPinnedRuns(current.filter((run) => run !== runCommand));
452
+ if (current.includes(runCommand2)) {
453
+ setPinnedRuns(current.filter((run) => run !== runCommand2));
454
454
  return;
455
455
  }
456
- setPinnedRuns([runCommand, ...current.filter((run) => run !== runCommand)]);
456
+ setPinnedRuns([runCommand2, ...current.filter((run) => run !== runCommand2)]);
457
457
  }
458
- function isPinnedRun(runCommand) {
459
- return getPinnedRuns().includes(runCommand);
458
+ function isPinnedRun(runCommand2) {
459
+ return getPinnedRuns().includes(runCommand2);
460
460
  }
461
461
 
462
462
  // src/data/commands.ts
@@ -628,14 +628,14 @@ function buildMainMenuItems({
628
628
  kind: "header",
629
629
  selectable: false
630
630
  });
631
- for (const runCommand of pinnedRuns) {
632
- const baseCommand = runCommand.split(" ").filter(Boolean)[0] ?? "";
631
+ for (const runCommand2 of pinnedRuns) {
632
+ const baseCommand = runCommand2.split(" ").filter(Boolean)[0] ?? "";
633
633
  const info = getCommandInfo(baseCommand);
634
634
  const infoHint = info ? `${info.categoryIcon} ${info.categoryLabel} \xB7 exact pinned run` : "Exact pinned run";
635
635
  nextItems.push({
636
- id: `run:${runCommand}`,
637
- value: runCommand,
638
- label: runCommand,
636
+ id: `run:${runCommand2}`,
637
+ value: runCommand2,
638
+ label: runCommand2,
639
639
  hint: infoHint,
640
640
  icon: "\u25B6",
641
641
  kind: "run",
@@ -711,6 +711,14 @@ function buildMainMenuItems({
711
711
  icon: "\u270F\uFE0F",
712
712
  kind: "action"
713
713
  });
714
+ nextItems.push({
715
+ id: "action-update",
716
+ value: "__action_update__",
717
+ label: "Update Polterbase",
718
+ hint: "Update the current repo install or the global install",
719
+ icon: "\u2B06\uFE0F",
720
+ kind: "action"
721
+ });
714
722
  nextItems.push({
715
723
  id: "action-exit",
716
724
  value: "__action_exit__",
@@ -772,6 +780,10 @@ function MainMenu({
772
780
  onNavigate("custom-command");
773
781
  return;
774
782
  }
783
+ if (value === "__action_update__") {
784
+ onNavigate("self-update");
785
+ return;
786
+ }
775
787
  if (value === "__action_exit__") {
776
788
  onExit();
777
789
  }
@@ -788,12 +800,12 @@ function MainMenu({
788
800
  return;
789
801
  }
790
802
  if (item.kind === "run") {
791
- const runCommand = item.value;
792
- const wasPinned = pinnedRunSet.has(runCommand);
793
- togglePinnedRun(runCommand);
803
+ const runCommand2 = item.value;
804
+ const wasPinned = pinnedRunSet.has(runCommand2);
805
+ togglePinnedRun(runCommand2);
794
806
  refreshPins();
795
807
  setPinFeedback(
796
- wasPinned ? `Unpinned exact run "${runCommand}"` : `Pinned exact run "${runCommand}"`
808
+ wasPinned ? `Unpinned exact run "${runCommand2}"` : `Pinned exact run "${runCommand2}"`
797
809
  );
798
810
  }
799
811
  };
@@ -968,8 +980,8 @@ function buildCommandArgItems({
968
980
  selectable: false
969
981
  },
970
982
  ...suggestions.map((option) => {
971
- const runCommand = buildRunCommand(command, option.args);
972
- const pinHint = pinnedRunSet.has(runCommand) ? "pinned run" : void 0;
983
+ const runCommand2 = buildRunCommand(command, option.args);
984
+ const pinHint = pinnedRunSet.has(runCommand2) ? "pinned run" : void 0;
973
985
  return {
974
986
  value: `suggest:${option.value}`,
975
987
  label: option.label,
@@ -1110,33 +1122,33 @@ function CommandArgs({
1110
1122
  return;
1111
1123
  }
1112
1124
  if (value.startsWith("suggest:")) {
1113
- const runCommand = getRunCommandFromArgsSelection(
1125
+ const runCommand2 = getRunCommandFromArgsSelection(
1114
1126
  command,
1115
1127
  value,
1116
1128
  suggestions
1117
1129
  );
1118
- if (runCommand) {
1130
+ if (runCommand2) {
1119
1131
  navigateWithExtraArgs(
1120
- runCommand.split(" ").slice(1).filter(Boolean)
1132
+ runCommand2.split(" ").slice(1).filter(Boolean)
1121
1133
  );
1122
1134
  }
1123
1135
  return;
1124
1136
  }
1125
1137
  },
1126
1138
  onRightAction: (item) => {
1127
- const runCommand = getRunCommandFromArgsSelection(
1139
+ const runCommand2 = getRunCommandFromArgsSelection(
1128
1140
  command,
1129
1141
  item.value,
1130
1142
  suggestions
1131
1143
  );
1132
- if (!runCommand) {
1144
+ if (!runCommand2) {
1133
1145
  return;
1134
1146
  }
1135
- const wasPinned = pinnedRuns.includes(runCommand);
1136
- togglePinnedRun(runCommand);
1147
+ const wasPinned = pinnedRuns.includes(runCommand2);
1148
+ togglePinnedRun(runCommand2);
1137
1149
  setPinnedRuns2(getPinnedRuns());
1138
1150
  setPinFeedback(
1139
- wasPinned ? `Unpinned exact run "${runCommand}"` : `Pinned exact run "${runCommand}"`
1151
+ wasPinned ? `Unpinned exact run "${runCommand2}"` : `Pinned exact run "${runCommand2}"`
1140
1152
  );
1141
1153
  },
1142
1154
  onCancel: onBack,
@@ -1358,11 +1370,65 @@ import { useState as useState7, useCallback as useCallback2 } from "react";
1358
1370
 
1359
1371
  // src/lib/runner.ts
1360
1372
  import { spawn } from "child_process";
1361
- async function runSupabaseCommand(args) {
1362
- return new Promise((resolve) => {
1373
+ import { existsSync } from "fs";
1374
+ import { delimiter, dirname, join, resolve } from "path";
1375
+ function getSupabaseBinaryCandidates() {
1376
+ if (process.platform === "win32") {
1377
+ return ["supabase.cmd", "supabase.exe", "supabase"];
1378
+ }
1379
+ return ["supabase"];
1380
+ }
1381
+ function hasLocalSupabaseBinary(binDir) {
1382
+ return getSupabaseBinaryCandidates().some(
1383
+ (candidate) => existsSync(join(binDir, candidate))
1384
+ );
1385
+ }
1386
+ function getPathEnvKey(env) {
1387
+ return Object.keys(env).find((key) => key.toLowerCase() === "path") ?? "PATH";
1388
+ }
1389
+ function findLocalSupabaseBinDir(startDir = process.cwd()) {
1390
+ let currentDir = resolve(startDir);
1391
+ while (true) {
1392
+ const binDir = join(currentDir, "node_modules", ".bin");
1393
+ if (hasLocalSupabaseBinary(binDir)) {
1394
+ return binDir;
1395
+ }
1396
+ const parentDir = dirname(currentDir);
1397
+ if (parentDir === currentDir) {
1398
+ return void 0;
1399
+ }
1400
+ currentDir = parentDir;
1401
+ }
1402
+ }
1403
+ function resolveSupabaseCommand(startDir = process.cwd(), env = process.env) {
1404
+ const localBinDir = findLocalSupabaseBinDir(startDir);
1405
+ if (!localBinDir) {
1406
+ return {
1407
+ command: "supabase",
1408
+ env: { ...env },
1409
+ source: "path"
1410
+ };
1411
+ }
1412
+ const pathKey = getPathEnvKey(env);
1413
+ const currentPath = env[pathKey];
1414
+ return {
1415
+ command: "supabase",
1416
+ env: {
1417
+ ...env,
1418
+ [pathKey]: currentPath ? `${localBinDir}${delimiter}${currentPath}` : localBinDir
1419
+ },
1420
+ source: "repository",
1421
+ localBinDir
1422
+ };
1423
+ }
1424
+ async function runCommand(execution, args, cwd = process.cwd()) {
1425
+ return new Promise((resolve3) => {
1363
1426
  let stdout = "";
1364
1427
  let stderr = "";
1365
- const child = spawn("supabase", args, {
1428
+ const resolvedExecution = typeof execution === "string" ? { command: execution } : execution;
1429
+ const child = spawn(resolvedExecution.command, args, {
1430
+ cwd,
1431
+ env: resolvedExecution.env,
1366
1432
  shell: true,
1367
1433
  stdio: ["inherit", "pipe", "pipe"]
1368
1434
  });
@@ -1377,7 +1443,7 @@ async function runSupabaseCommand(args) {
1377
1443
  process.stderr.write(text);
1378
1444
  });
1379
1445
  child.on("error", (err) => {
1380
- resolve({
1446
+ resolve3({
1381
1447
  exitCode: null,
1382
1448
  signal: null,
1383
1449
  stdout,
@@ -1386,19 +1452,22 @@ async function runSupabaseCommand(args) {
1386
1452
  });
1387
1453
  });
1388
1454
  child.on("exit", (code, signal) => {
1389
- resolve({ exitCode: code, signal, stdout, stderr });
1455
+ resolve3({ exitCode: code, signal, stdout, stderr });
1390
1456
  });
1391
1457
  });
1392
1458
  }
1459
+ async function runSupabaseCommand(args, cwd = process.cwd()) {
1460
+ return runCommand(resolveSupabaseCommand(cwd), args, cwd);
1461
+ }
1393
1462
 
1394
1463
  // src/hooks/useCommand.ts
1395
- function useCommand() {
1464
+ function useCommand(execution = "supabase", cwd = process.cwd()) {
1396
1465
  const [status, setStatus] = useState7("idle");
1397
1466
  const [result, setResult] = useState7(null);
1398
1467
  const run = useCallback2(async (args) => {
1399
1468
  setStatus("running");
1400
1469
  setResult(null);
1401
- const res = await runSupabaseCommand(args);
1470
+ const res = execution === "supabase" ? await runSupabaseCommand(args, cwd) : await runCommand(execution, args, cwd);
1402
1471
  setResult(res);
1403
1472
  if (res.spawnError || res.exitCode !== null && res.exitCode !== 0) {
1404
1473
  setStatus("error");
@@ -1406,7 +1475,7 @@ function useCommand() {
1406
1475
  setStatus("success");
1407
1476
  }
1408
1477
  return res;
1409
- }, []);
1478
+ }, [cwd, execution]);
1410
1479
  const reset = useCallback2(() => {
1411
1480
  setStatus("idle");
1412
1481
  setResult(null);
@@ -1417,19 +1486,19 @@ function useCommand() {
1417
1486
  // src/lib/clipboard.ts
1418
1487
  import { spawn as spawn2, exec } from "child_process";
1419
1488
  async function openInBrowser(url) {
1420
- return new Promise((resolve) => {
1489
+ return new Promise((resolve3) => {
1421
1490
  const cmd = process.platform === "darwin" ? `open "${url}"` : process.platform === "win32" ? `start "${url}"` : `xdg-open "${url}"`;
1422
- exec(cmd, () => resolve());
1491
+ exec(cmd, () => resolve3());
1423
1492
  });
1424
1493
  }
1425
1494
  async function copyToClipboard(text) {
1426
- return new Promise((resolve) => {
1495
+ return new Promise((resolve3) => {
1427
1496
  const cmd = process.platform === "darwin" ? "pbcopy" : process.platform === "win32" ? "clip" : "xclip -selection clipboard";
1428
1497
  const child = spawn2(cmd, [], { shell: true });
1429
1498
  child.stdin?.write(text);
1430
1499
  child.stdin?.end();
1431
- child.on("exit", () => resolve());
1432
- child.on("error", () => resolve());
1500
+ child.on("exit", () => resolve3());
1501
+ child.on("error", () => resolve3());
1433
1502
  });
1434
1503
  }
1435
1504
 
@@ -1445,7 +1514,7 @@ function CommandExecution({
1445
1514
  const [pinMessage, setPinMessage] = useState8();
1446
1515
  const { status, result, run, reset } = useCommand();
1447
1516
  const cmdDisplay = `supabase ${currentArgs.join(" ")}`;
1448
- const runCommand = currentArgs.join(" ");
1517
+ const runCommand2 = currentArgs.join(" ");
1449
1518
  useEffect4(() => {
1450
1519
  if (phase === "running" && status === "idle") {
1451
1520
  run(currentArgs);
@@ -1453,7 +1522,7 @@ function CommandExecution({
1453
1522
  }, [phase, status, run, currentArgs]);
1454
1523
  useEffect4(() => {
1455
1524
  if (phase === "running" && status === "success") {
1456
- if (isPinnedRun(runCommand)) {
1525
+ if (isPinnedRun(runCommand2)) {
1457
1526
  setPhase("success");
1458
1527
  } else {
1459
1528
  setPhase("success-pin-offer");
@@ -1462,7 +1531,7 @@ function CommandExecution({
1462
1531
  if (phase === "running" && status === "error") {
1463
1532
  setPhase("error-menu");
1464
1533
  }
1465
- }, [phase, runCommand, status]);
1534
+ }, [phase, runCommand2, status]);
1466
1535
  if (phase === "confirm") {
1467
1536
  return /* @__PURE__ */ jsx13(Box12, { flexDirection: "column", children: /* @__PURE__ */ jsx13(
1468
1537
  ConfirmPrompt,
@@ -1504,8 +1573,8 @@ function CommandExecution({
1504
1573
  message: "Pin this exact command?",
1505
1574
  defaultValue: false,
1506
1575
  onConfirm: (shouldPin) => {
1507
- if (shouldPin && !isPinnedRun(runCommand)) {
1508
- togglePinnedRun(runCommand);
1576
+ if (shouldPin && !isPinnedRun(runCommand2)) {
1577
+ togglePinnedRun(runCommand2);
1509
1578
  setPinMessage("Exact command pinned to Pinned Runs.");
1510
1579
  }
1511
1580
  setPhase("success");
@@ -1569,7 +1638,7 @@ function CommandExecution({
1569
1638
  /* @__PURE__ */ jsx13(Text11, { color: "red", children: result.spawnError })
1570
1639
  ] }),
1571
1640
  (result.spawnError.includes("ENOENT") || result.spawnError.includes("not found")) && /* @__PURE__ */ jsxs13(Box12, { flexDirection: "column", marginLeft: 2, marginTop: 1, children: [
1572
- /* @__PURE__ */ jsx13(Text11, { color: inkColors.accent, bold: true, children: "\u{1F4A1} Supabase CLI not found in PATH" }),
1641
+ /* @__PURE__ */ jsx13(Text11, { color: inkColors.accent, bold: true, children: "\u{1F4A1} Supabase CLI not found in this repository or PATH" }),
1573
1642
  /* @__PURE__ */ jsxs13(Box12, { gap: 1, children: [
1574
1643
  /* @__PURE__ */ jsx13(Text11, { dimColor: true, children: "Install it:" }),
1575
1644
  /* @__PURE__ */ jsx13(Text11, { color: inkColors.accent, children: "https://supabase.com/docs/guides/cli" })
@@ -1634,8 +1703,244 @@ function CommandExecution({
1634
1703
  ] });
1635
1704
  }
1636
1705
 
1637
- // src/app.tsx
1706
+ // src/screens/SelfUpdate.tsx
1707
+ import { useEffect as useEffect5, useState as useState9 } from "react";
1708
+ import { Box as Box13, Text as Text12 } from "ink";
1709
+
1710
+ // src/lib/packageRoot.ts
1711
+ import { existsSync as existsSync2 } from "fs";
1712
+ import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
1713
+ function findNearestPackageRoot(startDir = process.cwd()) {
1714
+ let currentDir = resolve2(startDir);
1715
+ while (true) {
1716
+ if (existsSync2(join2(currentDir, "package.json"))) {
1717
+ return currentDir;
1718
+ }
1719
+ const parentDir = dirname2(currentDir);
1720
+ if (parentDir === currentDir) {
1721
+ return void 0;
1722
+ }
1723
+ currentDir = parentDir;
1724
+ }
1725
+ }
1726
+
1727
+ // src/screens/SelfUpdate.tsx
1638
1728
  import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1729
+ var packageName = "@polterware/polterbase";
1730
+ var globalUpdateArgs = ["install", "-g", `${packageName}@latest`];
1731
+ var repositoryUpdateArgs = ["install", "-D", `${packageName}@latest`];
1732
+ function getUpdateArgs(target) {
1733
+ return target === "repository" ? repositoryUpdateArgs : globalUpdateArgs;
1734
+ }
1735
+ function SelfUpdate({
1736
+ onBack,
1737
+ onExit
1738
+ }) {
1739
+ const repositoryRoot = findNearestPackageRoot();
1740
+ const [target, setTarget] = useState9(
1741
+ repositoryRoot ? "repository" : "global"
1742
+ );
1743
+ const [phase, setPhase] = useState9(
1744
+ repositoryRoot ? "target" : "confirm"
1745
+ );
1746
+ const updateArgs = getUpdateArgs(target);
1747
+ const updateDisplay = `npm ${updateArgs.join(" ")}`;
1748
+ const updateCwd = target === "repository" && repositoryRoot ? repositoryRoot : process.cwd();
1749
+ const { status, result, run, reset } = useCommand("npm", updateCwd);
1750
+ useEffect5(() => {
1751
+ if (phase === "running" && status === "idle") {
1752
+ run(updateArgs);
1753
+ }
1754
+ }, [phase, run, status, updateArgs]);
1755
+ useEffect5(() => {
1756
+ if (phase === "running" && status === "success") {
1757
+ setPhase("success");
1758
+ }
1759
+ if (phase === "running" && status === "error") {
1760
+ setPhase("error");
1761
+ }
1762
+ }, [phase, status]);
1763
+ if (phase === "target") {
1764
+ return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
1765
+ /* @__PURE__ */ jsx14(Box13, { marginBottom: 1, children: /* @__PURE__ */ jsx14(Text12, { bold: true, children: "Choose where to update Polterbase." }) }),
1766
+ /* @__PURE__ */ jsx14(
1767
+ SelectList,
1768
+ {
1769
+ items: [
1770
+ {
1771
+ value: "repository",
1772
+ label: "Current repository",
1773
+ hint: "Pin the latest version in package.json"
1774
+ },
1775
+ {
1776
+ value: "global",
1777
+ label: "Global install",
1778
+ hint: "Update the shared version available in PATH"
1779
+ },
1780
+ { value: "back", label: "\u2190 Back to menu" }
1781
+ ],
1782
+ onSelect: (value) => {
1783
+ if (value === "back") {
1784
+ onBack();
1785
+ return;
1786
+ }
1787
+ setTarget(value);
1788
+ reset();
1789
+ setPhase("confirm");
1790
+ },
1791
+ onCancel: onBack
1792
+ }
1793
+ ),
1794
+ repositoryRoot && /* @__PURE__ */ jsx14(Box13, { marginTop: 1, marginLeft: 2, children: /* @__PURE__ */ jsxs14(Text12, { dimColor: true, children: [
1795
+ "Repository root: ",
1796
+ repositoryRoot
1797
+ ] }) })
1798
+ ] });
1799
+ }
1800
+ if (phase === "confirm") {
1801
+ return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
1802
+ /* @__PURE__ */ jsx14(
1803
+ ConfirmPrompt,
1804
+ {
1805
+ message: `Run ${updateDisplay}?`,
1806
+ defaultValue: true,
1807
+ onConfirm: (confirmed) => {
1808
+ if (confirmed) {
1809
+ reset();
1810
+ setPhase("running");
1811
+ return;
1812
+ }
1813
+ if (repositoryRoot) {
1814
+ setPhase("target");
1815
+ return;
1816
+ }
1817
+ onBack();
1818
+ }
1819
+ }
1820
+ ),
1821
+ /* @__PURE__ */ jsxs14(Box13, { marginTop: 1, marginLeft: 2, flexDirection: "column", children: [
1822
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: target === "repository" ? "This updates the dependency in the current repository." : "This updates the global npm install." }),
1823
+ target === "repository" && repositoryRoot && /* @__PURE__ */ jsxs14(Text12, { dimColor: true, children: [
1824
+ "Run location: ",
1825
+ repositoryRoot
1826
+ ] })
1827
+ ] })
1828
+ ] });
1829
+ }
1830
+ if (phase === "running") {
1831
+ return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
1832
+ /* @__PURE__ */ jsx14(Divider, {}),
1833
+ /* @__PURE__ */ jsxs14(Box13, { marginY: 1, gap: 1, children: [
1834
+ /* @__PURE__ */ jsx14(Text12, { color: inkColors.accent, bold: true, children: "\u25B6" }),
1835
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Running:" }),
1836
+ /* @__PURE__ */ jsx14(Text12, { children: updateDisplay })
1837
+ ] }),
1838
+ /* @__PURE__ */ jsx14(Divider, {}),
1839
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx14(Spinner, { label: "Updating Polterbase..." }) })
1840
+ ] });
1841
+ }
1842
+ if (phase === "success") {
1843
+ return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
1844
+ /* @__PURE__ */ jsx14(Divider, {}),
1845
+ /* @__PURE__ */ jsxs14(Box13, { marginY: 1, gap: 1, children: [
1846
+ /* @__PURE__ */ jsx14(Text12, { color: inkColors.accent, bold: true, children: "\u2713" }),
1847
+ /* @__PURE__ */ jsx14(Text12, { color: inkColors.accent, bold: true, children: "Update completed successfully!" })
1848
+ ] }),
1849
+ /* @__PURE__ */ jsxs14(Box13, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: [
1850
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Restart Polterbase to use the latest version." }),
1851
+ target === "repository" && repositoryRoot && /* @__PURE__ */ jsxs14(Text12, { dimColor: true, children: [
1852
+ "Repository updated in: ",
1853
+ repositoryRoot
1854
+ ] })
1855
+ ] }),
1856
+ /* @__PURE__ */ jsx14(
1857
+ SelectList,
1858
+ {
1859
+ items: [
1860
+ { value: "__back__", label: "\u2190 Back to menu" },
1861
+ { value: "__exit__", label: "\u{1F6AA} Exit Polterbase" }
1862
+ ],
1863
+ onSelect: (value) => {
1864
+ if (value === "__exit__") {
1865
+ onExit();
1866
+ return;
1867
+ }
1868
+ onBack();
1869
+ },
1870
+ onCancel: onBack
1871
+ }
1872
+ )
1873
+ ] });
1874
+ }
1875
+ return /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", children: [
1876
+ /* @__PURE__ */ jsx14(Divider, {}),
1877
+ result?.spawnError ? /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", marginY: 1, children: [
1878
+ /* @__PURE__ */ jsxs14(Box13, { gap: 1, children: [
1879
+ /* @__PURE__ */ jsx14(Text12, { color: "red", bold: true, children: "\u2717" }),
1880
+ /* @__PURE__ */ jsx14(Text12, { color: "red", bold: true, children: "Failed to start update" })
1881
+ ] }),
1882
+ /* @__PURE__ */ jsxs14(Box13, { marginLeft: 2, marginTop: 1, children: [
1883
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Error: " }),
1884
+ /* @__PURE__ */ jsx14(Text12, { color: "red", children: result.spawnError })
1885
+ ] })
1886
+ ] }) : /* @__PURE__ */ jsxs14(Box13, { flexDirection: "column", marginY: 1, children: [
1887
+ /* @__PURE__ */ jsxs14(Box13, { gap: 1, children: [
1888
+ /* @__PURE__ */ jsx14(Text12, { color: "red", bold: true, children: "\u2717" }),
1889
+ /* @__PURE__ */ jsx14(Text12, { color: "red", children: "Update failed " }),
1890
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(exit code " }),
1891
+ /* @__PURE__ */ jsx14(Text12, { color: "red", bold: true, children: String(result?.exitCode) }),
1892
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ")" })
1893
+ ] }),
1894
+ /* @__PURE__ */ jsxs14(Box13, { marginLeft: 2, marginTop: 1, children: [
1895
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Command: " }),
1896
+ /* @__PURE__ */ jsx14(Text12, { children: updateDisplay })
1897
+ ] })
1898
+ ] }),
1899
+ /* @__PURE__ */ jsxs14(Box13, { marginBottom: 1, marginLeft: 2, flexDirection: "column", children: [
1900
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Manual fallback:" }),
1901
+ /* @__PURE__ */ jsx14(Text12, { color: inkColors.accent, children: updateDisplay }),
1902
+ target === "repository" && repositoryRoot && /* @__PURE__ */ jsxs14(Text12, { dimColor: true, children: [
1903
+ "Run location: ",
1904
+ repositoryRoot
1905
+ ] })
1906
+ ] }),
1907
+ /* @__PURE__ */ jsx14(Box13, { marginTop: 1, marginBottom: 1, children: /* @__PURE__ */ jsx14(Text12, { bold: true, children: "What would you like to do?" }) }),
1908
+ /* @__PURE__ */ jsx14(
1909
+ SelectList,
1910
+ {
1911
+ items: [
1912
+ { value: "retry", label: "\u{1F504} Retry update" },
1913
+ ...repositoryRoot ? [{ value: "target", label: "\u2194 Choose update target" }] : [],
1914
+ { value: "menu", label: "\u2190 Return to main menu" },
1915
+ { value: "exit", label: "\u{1F6AA} Exit Polterbase" }
1916
+ ],
1917
+ onSelect: (value) => {
1918
+ switch (value) {
1919
+ case "retry":
1920
+ reset();
1921
+ setPhase("running");
1922
+ break;
1923
+ case "target":
1924
+ reset();
1925
+ setPhase("target");
1926
+ break;
1927
+ case "menu":
1928
+ onBack();
1929
+ break;
1930
+ case "exit":
1931
+ onExit();
1932
+ break;
1933
+ }
1934
+ },
1935
+ onCancel: onBack
1936
+ }
1937
+ ),
1938
+ /* @__PURE__ */ jsx14(StatusBar, {})
1939
+ ] });
1940
+ }
1941
+
1942
+ // src/app.tsx
1943
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
1639
1944
  function App() {
1640
1945
  const { screen, params, navigate, goBack } = useNavigation();
1641
1946
  const { exit } = useApp();
@@ -1647,9 +1952,9 @@ function App() {
1647
1952
  };
1648
1953
  switch (screen) {
1649
1954
  case "main-menu":
1650
- return /* @__PURE__ */ jsx14(MainMenu, { onNavigate: navigate, onExit: handleExit });
1955
+ return /* @__PURE__ */ jsx15(MainMenu, { onNavigate: navigate, onExit: handleExit });
1651
1956
  case "command-args":
1652
- return /* @__PURE__ */ jsx14(
1957
+ return /* @__PURE__ */ jsx15(
1653
1958
  CommandArgs,
1654
1959
  {
1655
1960
  command: params.command ?? "",
@@ -1658,9 +1963,9 @@ function App() {
1658
1963
  }
1659
1964
  );
1660
1965
  case "custom-command":
1661
- return /* @__PURE__ */ jsx14(CustomCommand, { onNavigate: navigate, onBack: goBack });
1966
+ return /* @__PURE__ */ jsx15(CustomCommand, { onNavigate: navigate, onBack: goBack });
1662
1967
  case "flag-selection":
1663
- return /* @__PURE__ */ jsx14(
1968
+ return /* @__PURE__ */ jsx15(
1664
1969
  FlagSelection,
1665
1970
  {
1666
1971
  args: params.args ?? [],
@@ -1670,7 +1975,7 @@ function App() {
1670
1975
  );
1671
1976
  case "confirm-execute":
1672
1977
  case "command-execution":
1673
- return /* @__PURE__ */ jsx14(
1978
+ return /* @__PURE__ */ jsx15(
1674
1979
  CommandExecution,
1675
1980
  {
1676
1981
  args: params.args ?? [],
@@ -1678,8 +1983,10 @@ function App() {
1678
1983
  onExit: handleExit
1679
1984
  }
1680
1985
  );
1986
+ case "self-update":
1987
+ return /* @__PURE__ */ jsx15(SelfUpdate, { onBack: goBack, onExit: handleExit });
1681
1988
  default:
1682
- return /* @__PURE__ */ jsx14(Box13, { children: /* @__PURE__ */ jsxs14(Text12, { color: "red", children: [
1989
+ return /* @__PURE__ */ jsx15(Box14, { children: /* @__PURE__ */ jsxs15(Text13, { color: "red", children: [
1683
1990
  "Unknown screen: ",
1684
1991
  screen
1685
1992
  ] }) });
@@ -1687,4 +1994,4 @@ function App() {
1687
1994
  }
1688
1995
 
1689
1996
  // src/index.tsx
1690
- render(React7.createElement(App));
1997
+ render(React8.createElement(App));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@polterware/polterbase",
3
- "version": "0.2.4",
4
- "description": "A global CLI for managing Supabase CLI workflows.",
3
+ "version": "0.2.5",
4
+ "description": "An interactive CLI for managing Supabase CLI workflows.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "polterbase": "./dist/index.js"
@@ -22,7 +22,6 @@
22
22
  "publishConfig": {
23
23
  "access": "public"
24
24
  },
25
- "preferGlobal": true,
26
25
  "scripts": {
27
26
  "dev": "tsup src/index.tsx --watch --format esm --dts",
28
27
  "build": "tsup src/index.tsx --format esm --clean",