@polterware/polter 0.2.1 → 0.2.3

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.
package/dist/api.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  runCommand,
21
21
  savePipeline,
22
22
  toolFlags
23
- } from "./chunk-3XNNC5AW.js";
23
+ } from "./chunk-2B2UWOVQ.js";
24
24
  export {
25
25
  allCommands,
26
26
  applyActions,
@@ -12,21 +12,24 @@ var supabaseCommands = [
12
12
  tool: "supabase",
13
13
  base: ["bootstrap"],
14
14
  label: "bootstrap",
15
- hint: "Bootstrap from a starter template"
15
+ hint: "Bootstrap from a starter template",
16
+ interactive: true
16
17
  },
17
18
  {
18
19
  id: "supabase:init",
19
20
  tool: "supabase",
20
21
  base: ["init"],
21
22
  label: "init",
22
- hint: "Initialize a local project"
23
+ hint: "Initialize a local project",
24
+ interactive: true
23
25
  },
24
26
  {
25
27
  id: "supabase:login",
26
28
  tool: "supabase",
27
29
  base: ["login"],
28
30
  label: "login",
29
- hint: "Authenticate with access token"
31
+ hint: "Authenticate with access token",
32
+ interactive: true
30
33
  },
31
34
  {
32
35
  id: "supabase:logout",
@@ -131,7 +134,8 @@ var supabaseCommands = [
131
134
  tool: "supabase",
132
135
  base: ["link"],
133
136
  label: "link",
134
- hint: "Link to remote project"
137
+ hint: "Link to remote project",
138
+ interactive: true
135
139
  },
136
140
  {
137
141
  id: "supabase:unlink",
@@ -325,7 +329,8 @@ var ghCommands = [
325
329
  tool: "gh",
326
330
  base: ["repo", "create"],
327
331
  label: "repo create",
328
- hint: "Create a new repository"
332
+ hint: "Create a new repository",
333
+ interactive: true
329
334
  },
330
335
  {
331
336
  id: "gh:repo:view",
@@ -489,7 +494,8 @@ var ghCommands = [
489
494
  tool: "gh",
490
495
  base: ["auth", "login"],
491
496
  label: "auth login",
492
- hint: "Log in to GitHub"
497
+ hint: "Log in to GitHub",
498
+ interactive: true
493
499
  },
494
500
  {
495
501
  id: "gh:auth:status",
@@ -610,14 +616,16 @@ var vercelCommands = [
610
616
  tool: "vercel",
611
617
  base: ["link"],
612
618
  label: "link",
613
- hint: "Link to a Vercel project"
619
+ hint: "Link to a Vercel project",
620
+ interactive: true
614
621
  },
615
622
  {
616
623
  id: "vercel:login",
617
624
  tool: "vercel",
618
625
  base: ["login"],
619
626
  label: "login",
620
- hint: "Log in to Vercel"
627
+ hint: "Log in to Vercel",
628
+ interactive: true
621
629
  },
622
630
  {
623
631
  id: "vercel:whoami",
@@ -863,7 +871,7 @@ function getFlagsForTool(toolId) {
863
871
  }
864
872
 
865
873
  // src/lib/runner.ts
866
- import { spawn } from "child_process";
874
+ import { spawn, spawnSync } from "child_process";
867
875
  import { existsSync } from "fs";
868
876
  import { delimiter, dirname, join, resolve } from "path";
869
877
  function getSupabaseBinaryCandidates() {
@@ -953,10 +961,30 @@ async function runCommand(execution, args, cwd = process.cwd(), options) {
953
961
  });
954
962
  });
955
963
  }
964
+ function runInteractiveCommand(execution, args, cwd = process.cwd()) {
965
+ const resolved = typeof execution === "string" ? { command: execution } : execution;
966
+ const result = spawnSync(resolved.command, args, {
967
+ cwd,
968
+ env: resolved.env,
969
+ shell: true,
970
+ stdio: "inherit"
971
+ });
972
+ return {
973
+ exitCode: result.status,
974
+ signal: result.signal,
975
+ stdout: "",
976
+ stderr: "",
977
+ spawnError: result.error?.message
978
+ };
979
+ }
956
980
  async function runSupabaseCommand(args, cwd = process.cwd()) {
957
981
  return runCommand(resolveSupabaseCommand(cwd), args, cwd);
958
982
  }
959
983
 
984
+ // src/lib/toolResolver.ts
985
+ import { existsSync as existsSync2, readFileSync } from "fs";
986
+ import { join as join2 } from "path";
987
+
960
988
  // src/lib/system.ts
961
989
  import { execSync } from "child_process";
962
990
  function commandExists(command) {
@@ -1029,6 +1057,63 @@ function getToolInfo(toolId) {
1029
1057
  toolInfoCache.set(toolId, info);
1030
1058
  return info;
1031
1059
  }
1060
+ function detectSupabaseLink(cwd) {
1061
+ try {
1062
+ const configPath = join2(cwd, ".supabase", "config.toml");
1063
+ if (existsSync2(configPath)) {
1064
+ const content = readFileSync(configPath, "utf-8");
1065
+ const match = content.match(/project_id\s*=\s*"([^"]+)"/);
1066
+ return { linked: true, project: match?.[1] };
1067
+ }
1068
+ } catch {
1069
+ }
1070
+ return { linked: false };
1071
+ }
1072
+ function detectVercelLink(cwd) {
1073
+ try {
1074
+ const projectPath = join2(cwd, ".vercel", "project.json");
1075
+ if (existsSync2(projectPath)) {
1076
+ const data = JSON.parse(readFileSync(projectPath, "utf-8"));
1077
+ return { linked: true, project: data.projectId };
1078
+ }
1079
+ } catch {
1080
+ }
1081
+ return { linked: false };
1082
+ }
1083
+ function detectGhLink(cwd) {
1084
+ try {
1085
+ const remote = execCapture(`git -C "${cwd}" remote get-url origin 2>/dev/null`);
1086
+ if (remote) {
1087
+ const match = remote.match(/[/:]([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
1088
+ return { linked: true, project: match?.[1] ?? remote };
1089
+ }
1090
+ } catch {
1091
+ }
1092
+ return { linked: false };
1093
+ }
1094
+ var toolLinkCache = /* @__PURE__ */ new Map();
1095
+ function getToolLinkInfo(toolId, cwd = process.cwd()) {
1096
+ const cached = toolLinkCache.get(toolId);
1097
+ if (cached) return cached;
1098
+ const base = getToolInfo(toolId);
1099
+ let linkStatus = { linked: false };
1100
+ if (base.installed) {
1101
+ switch (toolId) {
1102
+ case "supabase":
1103
+ linkStatus = detectSupabaseLink(cwd);
1104
+ break;
1105
+ case "vercel":
1106
+ linkStatus = detectVercelLink(cwd);
1107
+ break;
1108
+ case "gh":
1109
+ linkStatus = detectGhLink(cwd);
1110
+ break;
1111
+ }
1112
+ }
1113
+ const info = { ...base, ...linkStatus };
1114
+ toolLinkCache.set(toolId, info);
1115
+ return info;
1116
+ }
1032
1117
 
1033
1118
  // src/pipeline/engine.ts
1034
1119
  async function executePipeline(pipeline, onProgress, cwd = process.cwd()) {
@@ -1076,19 +1161,19 @@ async function executePipeline(pipeline, onProgress, cwd = process.cwd()) {
1076
1161
  }
1077
1162
 
1078
1163
  // src/config/projectConfig.ts
1079
- import { existsSync as existsSync3, readFileSync, writeFileSync, mkdirSync } from "fs";
1080
- import { join as join3 } from "path";
1164
+ import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
1165
+ import { join as join4 } from "path";
1081
1166
 
1082
1167
  // src/lib/packageRoot.ts
1083
- import { existsSync as existsSync2 } from "fs";
1084
- import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
1168
+ import { existsSync as existsSync3 } from "fs";
1169
+ import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
1085
1170
  var rootCache = /* @__PURE__ */ new Map();
1086
1171
  function findNearestPackageRoot(startDir = process.cwd()) {
1087
1172
  const resolvedStart = resolve2(startDir);
1088
1173
  if (rootCache.has(resolvedStart)) return rootCache.get(resolvedStart);
1089
1174
  let currentDir = resolvedStart;
1090
1175
  while (true) {
1091
- if (existsSync2(join2(currentDir, "package.json"))) {
1176
+ if (existsSync3(join3(currentDir, "package.json"))) {
1092
1177
  rootCache.set(resolvedStart, currentDir);
1093
1178
  return currentDir;
1094
1179
  }
@@ -1114,15 +1199,15 @@ function defaultConfig() {
1114
1199
  function getProjectConfigPath(startDir) {
1115
1200
  const root = findNearestPackageRoot(startDir);
1116
1201
  if (!root) return void 0;
1117
- const dir = join3(root, CONFIG_DIR);
1118
- return { dir, file: join3(dir, CONFIG_FILE) };
1202
+ const dir = join4(root, CONFIG_DIR);
1203
+ return { dir, file: join4(dir, CONFIG_FILE) };
1119
1204
  }
1120
1205
  function readProjectConfig(startDir) {
1121
1206
  const paths = getProjectConfigPath(startDir);
1122
1207
  if (!paths) return void 0;
1123
- if (!existsSync3(paths.file)) return void 0;
1208
+ if (!existsSync4(paths.file)) return void 0;
1124
1209
  try {
1125
- const raw = readFileSync(paths.file, "utf-8");
1210
+ const raw = readFileSync2(paths.file, "utf-8");
1126
1211
  return JSON.parse(raw);
1127
1212
  } catch {
1128
1213
  return void 0;
@@ -1221,8 +1306,8 @@ function findPipelineByName(name, startDir) {
1221
1306
  }
1222
1307
 
1223
1308
  // src/declarative/parser.ts
1224
- import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
1225
- import { join as join4 } from "path";
1309
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1310
+ import { join as join5 } from "path";
1226
1311
  var YAML_FILE = "polter.yaml";
1227
1312
  function parseSimpleYaml(content) {
1228
1313
  const lines = content.split("\n");
@@ -1294,13 +1379,13 @@ function parseValue(raw) {
1294
1379
  return raw;
1295
1380
  }
1296
1381
  function findPolterYaml(startDir = process.cwd()) {
1297
- const filePath = join4(startDir, YAML_FILE);
1298
- return existsSync4(filePath) ? filePath : void 0;
1382
+ const filePath = join5(startDir, YAML_FILE);
1383
+ return existsSync5(filePath) ? filePath : void 0;
1299
1384
  }
1300
1385
  function parsePolterYaml(startDir = process.cwd()) {
1301
1386
  const filePath = findPolterYaml(startDir);
1302
1387
  if (!filePath) return void 0;
1303
- const content = readFileSync2(filePath, "utf-8");
1388
+ const content = readFileSync3(filePath, "utf-8");
1304
1389
  const raw = parseSimpleYaml(content);
1305
1390
  return raw;
1306
1391
  }
@@ -1451,10 +1536,12 @@ export {
1451
1536
  toolFlags,
1452
1537
  getFlagsForTool,
1453
1538
  runCommand,
1539
+ runInteractiveCommand,
1454
1540
  runSupabaseCommand,
1455
1541
  commandExists,
1456
1542
  resolveToolCommand,
1457
1543
  getToolInfo,
1544
+ getToolLinkInfo,
1458
1545
  executePipeline,
1459
1546
  findNearestPackageRoot,
1460
1547
  getProjectConfigPath,
package/dist/index.js CHANGED
@@ -17,14 +17,16 @@ import {
17
17
  getOrCreateProjectConfig,
18
18
  getProjectConfigPath,
19
19
  getToolInfo,
20
+ getToolLinkInfo,
20
21
  parsePolterYaml,
21
22
  planChanges,
22
23
  resolveToolCommand,
23
24
  runCommand,
25
+ runInteractiveCommand,
24
26
  runSupabaseCommand,
25
27
  savePipeline,
26
28
  writeProjectConfig
27
- } from "./chunk-3XNNC5AW.js";
29
+ } from "./chunk-2B2UWOVQ.js";
28
30
 
29
31
  // src/index.tsx
30
32
  import React20 from "react";
@@ -176,16 +178,16 @@ var ghost = {
176
178
  // src/components/GhostBanner.tsx
177
179
  import { jsx, jsxs } from "react/jsx-runtime";
178
180
  var ToolStatusBadges = React.memo(function ToolStatusBadges2() {
179
- const tools = ["supabase", "gh", "vercel"].map(getToolInfo);
181
+ const tools = ["supabase", "gh", "vercel"].map((id) => getToolLinkInfo(id));
180
182
  return /* @__PURE__ */ jsx(Box, { gap: 1, children: tools.map((t) => /* @__PURE__ */ jsxs(
181
183
  Text,
182
184
  {
183
- color: t.installed ? inkColors.accent : "red",
185
+ color: t.linked ? inkColors.accent : t.installed ? "yellow" : "red",
184
186
  dimColor: !t.installed,
185
187
  children: [
186
188
  t.id,
187
189
  ":",
188
- t.installed ? "ok" : "x"
190
+ t.linked ? "linked" : t.installed ? "ok" : "x"
189
191
  ]
190
192
  },
191
193
  t.id
@@ -1127,7 +1129,8 @@ function CommandArgs({
1127
1129
  onNavigate("flag-selection", {
1128
1130
  args: [...baseArgs, ...extraArgs],
1129
1131
  command,
1130
- tool: resolvedTool
1132
+ tool: resolvedTool,
1133
+ interactive: cmdDef?.interactive
1131
1134
  });
1132
1135
  };
1133
1136
  useInput4((_input, key) => {
@@ -1427,6 +1430,7 @@ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
1427
1430
  function FlagSelection({
1428
1431
  args,
1429
1432
  tool = "supabase",
1433
+ interactive,
1430
1434
  onNavigate,
1431
1435
  onBack,
1432
1436
  width = 80,
@@ -1436,7 +1440,7 @@ function FlagSelection({
1436
1440
  }) {
1437
1441
  const flags = getFlagsForTool(tool);
1438
1442
  if (flags.length === 0) {
1439
- onNavigate("confirm-execute", { args, tool });
1443
+ onNavigate("confirm-execute", { args, tool, interactive });
1440
1444
  return /* @__PURE__ */ jsx11(Box10, {});
1441
1445
  }
1442
1446
  const cmdDisplay = `${tool} ${args.join(" ")}`;
@@ -1460,7 +1464,7 @@ function FlagSelection({
1460
1464
  flags,
1461
1465
  onSubmit: (selectedFlags) => {
1462
1466
  const finalArgs = selectedFlags.length > 0 ? [...args, ...selectedFlags] : args;
1463
- onNavigate("confirm-execute", { args: finalArgs, tool });
1467
+ onNavigate("confirm-execute", { args: finalArgs, tool, interactive });
1464
1468
  },
1465
1469
  onCancel: onBack,
1466
1470
  isInputActive,
@@ -1486,7 +1490,7 @@ function FlagSelection({
1486
1490
  }
1487
1491
 
1488
1492
  // src/screens/CommandExecution.tsx
1489
- import { useState as useState11, useEffect as useEffect5 } from "react";
1493
+ import { useState as useState11, useEffect as useEffect6 } from "react";
1490
1494
  import { Box as Box13, Text as Text17 } from "ink";
1491
1495
 
1492
1496
  // src/components/Spinner.tsx
@@ -1668,6 +1672,58 @@ function useCommand(execution = "supabase", cwd = process.cwd(), options) {
1668
1672
  return { status, result, run, reset };
1669
1673
  }
1670
1674
 
1675
+ // src/hooks/useInteractiveRun.ts
1676
+ import { useCallback as useCallback3 } from "react";
1677
+
1678
+ // src/hooks/useFullscreen.ts
1679
+ import { useEffect as useEffect5 } from "react";
1680
+ var ENTER_ALT_SCREEN = "\x1B[?1049h";
1681
+ var LEAVE_ALT_SCREEN = "\x1B[?1049l";
1682
+ var HIDE_CURSOR = "\x1B[?25l";
1683
+ var SHOW_CURSOR = "\x1B[?25h";
1684
+ function useFullscreen() {
1685
+ useEffect5(() => {
1686
+ process.stdout.write(ENTER_ALT_SCREEN + HIDE_CURSOR);
1687
+ return () => {
1688
+ process.stdout.write(SHOW_CURSOR + LEAVE_ALT_SCREEN);
1689
+ };
1690
+ }, []);
1691
+ }
1692
+
1693
+ // src/hooks/useInteractiveRun.ts
1694
+ function useInteractiveRun() {
1695
+ const runInteractive = useCallback3(
1696
+ (tool, args, cwd) => {
1697
+ process.stdout.write(SHOW_CURSOR + LEAVE_ALT_SCREEN + "\x1B[2J\x1B[H");
1698
+ if (process.stdin.isTTY) {
1699
+ process.stdin.setRawMode(false);
1700
+ }
1701
+ process.stdout.write(`
1702
+ Running: ${tool} ${args.join(" ")}
1703
+
1704
+ `);
1705
+ const resolved = resolveToolCommand(tool, cwd);
1706
+ const result = runInteractiveCommand(
1707
+ { command: resolved.command, env: resolved.env },
1708
+ args,
1709
+ cwd
1710
+ );
1711
+ process.stdout.write(
1712
+ `
1713
+ Command exited (code ${result.exitCode ?? "?"}). Returning to Polter...
1714
+ `
1715
+ );
1716
+ if (process.stdin.isTTY) {
1717
+ process.stdin.setRawMode(true);
1718
+ }
1719
+ process.stdout.write(ENTER_ALT_SCREEN + HIDE_CURSOR);
1720
+ return result;
1721
+ },
1722
+ []
1723
+ );
1724
+ return { runInteractive };
1725
+ }
1726
+
1671
1727
  // src/lib/clipboard.ts
1672
1728
  import { spawn, exec } from "child_process";
1673
1729
  async function copyToClipboard(text) {
@@ -1731,6 +1787,7 @@ import { jsx as jsx17, jsxs as jsxs15 } from "react/jsx-runtime";
1731
1787
  function CommandExecution({
1732
1788
  args: initialArgs,
1733
1789
  tool = "supabase",
1790
+ interactive = false,
1734
1791
  onBack,
1735
1792
  onHome,
1736
1793
  onExit,
@@ -1746,14 +1803,24 @@ function CommandExecution({
1746
1803
  const { status, result, run, reset } = useCommand(tool, process.cwd(), {
1747
1804
  quiet: panelMode
1748
1805
  });
1806
+ const { runInteractive } = useInteractiveRun();
1749
1807
  const cmdDisplay = `${tool} ${currentArgs.join(" ")}`;
1750
1808
  const runCommand2 = currentArgs.join(" ");
1751
- useEffect5(() => {
1809
+ useEffect6(() => {
1752
1810
  if (phase === "running" && status === "idle") {
1753
- run(currentArgs);
1811
+ if (panelMode && interactive) {
1812
+ const interactiveResult = runInteractive(tool, currentArgs);
1813
+ if (!interactiveResult.spawnError && interactiveResult.exitCode === 0) {
1814
+ setPhase("success");
1815
+ } else {
1816
+ setPhase("error-menu");
1817
+ }
1818
+ } else {
1819
+ run(currentArgs);
1820
+ }
1754
1821
  }
1755
- }, [phase, status, run, currentArgs]);
1756
- useEffect5(() => {
1822
+ }, [phase, status, run, currentArgs, panelMode, interactive, tool, runInteractive]);
1823
+ useEffect6(() => {
1757
1824
  if (phase === "running" && status === "success") {
1758
1825
  if (isPinnedRun(runCommand2)) {
1759
1826
  setPhase("success");
@@ -1916,6 +1983,13 @@ function CommandExecution({
1916
1983
  });
1917
1984
  }
1918
1985
  }
1986
+ if (panelMode && !result?.spawnError) {
1987
+ errorItems.push({
1988
+ value: "run-interactive",
1989
+ label: "\u{1F4BB} Run in terminal",
1990
+ hint: "Suspend TUI and run interactively"
1991
+ });
1992
+ }
1919
1993
  errorItems.push({
1920
1994
  value: "copy",
1921
1995
  label: "\u{1F4CB} Copy command to clipboard"
@@ -1967,7 +2041,7 @@ function CommandExecution({
1967
2041
  {
1968
2042
  stdout: result?.stdout,
1969
2043
  stderr: result?.stderr,
1970
- height: Math.max(3, height - 16 - (suggestions.length > 0 ? suggestions.length + 4 : 0)),
2044
+ height: Math.max(3, height - 13 - errorItems.length - (suggestions.length > 0 ? suggestions.length + 4 : 0)),
1971
2045
  isActive: false
1972
2046
  }
1973
2047
  ),
@@ -2006,6 +2080,13 @@ function CommandExecution({
2006
2080
  setPhase("running");
2007
2081
  break;
2008
2082
  }
2083
+ case "run-interactive": {
2084
+ const interactiveResult = runInteractive(tool, currentArgs);
2085
+ if (!interactiveResult.spawnError && interactiveResult.exitCode === 0) {
2086
+ setPhase("success");
2087
+ }
2088
+ break;
2089
+ }
2009
2090
  case "copy":
2010
2091
  await copyToClipboard(cmdDisplay);
2011
2092
  break;
@@ -2031,7 +2112,7 @@ function CommandExecution({
2031
2112
  }
2032
2113
 
2033
2114
  // src/screens/SelfUpdate.tsx
2034
- import { useEffect as useEffect6, useState as useState12 } from "react";
2115
+ import { useEffect as useEffect7, useState as useState12 } from "react";
2035
2116
  import { Box as Box14, Text as Text18 } from "ink";
2036
2117
  import { jsx as jsx18, jsxs as jsxs16 } from "react/jsx-runtime";
2037
2118
  var packageName = "@polterware/polter";
@@ -2061,12 +2142,12 @@ function SelfUpdate({
2061
2142
  const { status, result, run, reset } = useCommand("npm", updateCwd, {
2062
2143
  quiet: panelMode
2063
2144
  });
2064
- useEffect6(() => {
2145
+ useEffect7(() => {
2065
2146
  if (phase === "running" && status === "idle") {
2066
2147
  run(updateArgs);
2067
2148
  }
2068
2149
  }, [phase, run, status, updateArgs]);
2069
- useEffect6(() => {
2150
+ useEffect7(() => {
2070
2151
  if (phase === "running" && status === "success") {
2071
2152
  setPhase("success");
2072
2153
  }
@@ -2311,15 +2392,16 @@ import { useMemo as useMemo4 } from "react";
2311
2392
  import { Box as Box15, Text as Text19 } from "ink";
2312
2393
  import { jsx as jsx19, jsxs as jsxs17 } from "react/jsx-runtime";
2313
2394
  var toolIds = ["supabase", "gh", "vercel", "git"];
2395
+ var linkableTools = /* @__PURE__ */ new Set(["supabase", "gh", "vercel"]);
2314
2396
  function ToolStatus({ onBack, width = 80, height = 24, panelMode = false, isInputActive = true }) {
2315
- const tools = useMemo4(() => toolIds.map(getToolInfo), []);
2397
+ const tools = useMemo4(() => toolIds.map((id) => getToolLinkInfo(id)), []);
2316
2398
  if (panelMode) {
2317
2399
  const statusItems = [
2318
2400
  { value: "__section__", label: "Installed Tools", kind: "header", selectable: false },
2319
2401
  ...tools.map((tool) => ({
2320
2402
  value: tool.id,
2321
2403
  label: `${tool.installed ? "\u2713" : "\u2717"} ${tool.label}`,
2322
- hint: tool.installed ? tool.version ?? "installed" : "not found",
2404
+ hint: tool.installed ? `${tool.version ?? "installed"}${linkableTools.has(tool.id) ? tool.linked ? ` \u2192 ${tool.project ? `linked (${tool.project})` : "linked"}` : " \u2192 not linked" : ""}` : "not found",
2323
2405
  kind: "action"
2324
2406
  }))
2325
2407
  ];
@@ -2344,7 +2426,7 @@ function ToolStatus({ onBack, width = 80, height = 24, panelMode = false, isInpu
2344
2426
  tools.map((tool) => /* @__PURE__ */ jsxs17(Box15, { gap: 1, marginLeft: 2, children: [
2345
2427
  /* @__PURE__ */ jsx19(Text19, { color: tool.installed ? inkColors.accent : "red", children: tool.installed ? "\u2713" : "\u2717" }),
2346
2428
  /* @__PURE__ */ jsx19(Box15, { width: 16, children: /* @__PURE__ */ jsx19(Text19, { bold: true, children: tool.label }) }),
2347
- /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: tool.installed ? tool.version ?? "installed" : "not found" })
2429
+ /* @__PURE__ */ jsx19(Text19, { dimColor: true, children: tool.installed ? `${tool.version ?? "installed"}${linkableTools.has(tool.id) ? tool.linked ? ` \u2192 ${tool.project ?? "linked"}` : " \u2192 not linked" : ""}` : "not found" })
2348
2430
  ] }, tool.id)),
2349
2431
  /* @__PURE__ */ jsx19(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx19(
2350
2432
  SelectList,
@@ -2365,7 +2447,7 @@ import { useMemo as useMemo5, useState as useState14 } from "react";
2365
2447
  import { Box as Box16, Text as Text20 } from "ink";
2366
2448
 
2367
2449
  // src/hooks/useEditor.ts
2368
- import { useState as useState13, useCallback as useCallback3 } from "react";
2450
+ import { useState as useState13, useCallback as useCallback4 } from "react";
2369
2451
  import { useStdin } from "ink";
2370
2452
 
2371
2453
  // src/lib/editor.ts
@@ -2417,7 +2499,7 @@ function openInEditor(filePath) {
2417
2499
  function useEditor() {
2418
2500
  const { setRawMode } = useStdin();
2419
2501
  const [isEditing, setIsEditing] = useState13(false);
2420
- const openEditor = useCallback3(async (filePath) => {
2502
+ const openEditor = useCallback4(async (filePath) => {
2421
2503
  const editor = resolveEditor();
2422
2504
  const terminal = isTerminalEditor(editor.command);
2423
2505
  setIsEditing(true);
@@ -2956,7 +3038,7 @@ function PipelineBuilder({
2956
3038
  }
2957
3039
 
2958
3040
  // src/screens/PipelineExecution.tsx
2959
- import { useState as useState16, useEffect as useEffect7, useMemo as useMemo7 } from "react";
3041
+ import { useState as useState16, useEffect as useEffect8, useMemo as useMemo7 } from "react";
2960
3042
  import { Box as Box21, Text as Text25 } from "ink";
2961
3043
 
2962
3044
  // src/components/StepIndicator.tsx
@@ -3052,7 +3134,7 @@ function PipelineExecution({
3052
3134
  const [phase, setPhase] = useState16("running");
3053
3135
  const [progress, setProgress] = useState16(null);
3054
3136
  const [results, setResults] = useState16([]);
3055
- useEffect7(() => {
3137
+ useEffect8(() => {
3056
3138
  if (!pipeline) return;
3057
3139
  executePipeline(pipeline, (p) => {
3058
3140
  setProgress({ ...p });
@@ -3259,23 +3341,8 @@ function AppClassic() {
3259
3341
  import React19 from "react";
3260
3342
  import { Box as Box30, Text as Text33, useApp as useApp2, useInput as useInput9 } from "ink";
3261
3343
 
3262
- // src/hooks/useFullscreen.ts
3263
- import { useEffect as useEffect8 } from "react";
3264
- var ENTER_ALT_SCREEN = "\x1B[?1049h";
3265
- var LEAVE_ALT_SCREEN = "\x1B[?1049l";
3266
- var HIDE_CURSOR = "\x1B[?25l";
3267
- var SHOW_CURSOR = "\x1B[?25h";
3268
- function useFullscreen() {
3269
- useEffect8(() => {
3270
- process.stdout.write(ENTER_ALT_SCREEN + HIDE_CURSOR);
3271
- return () => {
3272
- process.stdout.write(SHOW_CURSOR + LEAVE_ALT_SCREEN);
3273
- };
3274
- }, []);
3275
- }
3276
-
3277
3344
  // src/hooks/usePanelNavigation.ts
3278
- import { useState as useState17, useCallback as useCallback4 } from "react";
3345
+ import { useState as useState17, useCallback as useCallback5 } from "react";
3279
3346
  function usePanelNavigation() {
3280
3347
  const [state, setState] = useState17({
3281
3348
  view: "pipelines",
@@ -3284,7 +3351,7 @@ function usePanelNavigation() {
3284
3351
  innerParams: {},
3285
3352
  innerStack: []
3286
3353
  });
3287
- const selectSidebarItem = useCallback4((itemId) => {
3354
+ const selectSidebarItem = useCallback5((itemId) => {
3288
3355
  const featureIds = [
3289
3356
  "database",
3290
3357
  "functions",
@@ -3325,7 +3392,7 @@ function usePanelNavigation() {
3325
3392
  });
3326
3393
  }
3327
3394
  }, [state.featureId]);
3328
- const navigateInner = useCallback4((screen, params) => {
3395
+ const navigateInner = useCallback5((screen, params) => {
3329
3396
  setState((prev) => ({
3330
3397
  ...prev,
3331
3398
  innerStack: [...prev.innerStack, { screen: prev.innerScreen, params: prev.innerParams }],
@@ -3333,7 +3400,7 @@ function usePanelNavigation() {
3333
3400
  innerParams: params ?? {}
3334
3401
  }));
3335
3402
  }, []);
3336
- const goBackInner = useCallback4(() => {
3403
+ const goBackInner = useCallback5(() => {
3337
3404
  setState((prev) => {
3338
3405
  if (prev.innerStack.length === 0) {
3339
3406
  return { ...prev, innerScreen: "home", innerParams: {} };
@@ -3348,7 +3415,7 @@ function usePanelNavigation() {
3348
3415
  };
3349
3416
  });
3350
3417
  }, []);
3351
- const goHomeInner = useCallback4(() => {
3418
+ const goHomeInner = useCallback5(() => {
3352
3419
  setState((prev) => ({
3353
3420
  ...prev,
3354
3421
  innerScreen: "home",
@@ -3356,7 +3423,7 @@ function usePanelNavigation() {
3356
3423
  innerStack: []
3357
3424
  }));
3358
3425
  }, []);
3359
- const switchViewAndNavigate = useCallback4(
3426
+ const switchViewAndNavigate = useCallback5(
3360
3427
  (view, screen, params) => {
3361
3428
  setState((prev) => ({
3362
3429
  ...prev,
@@ -3379,14 +3446,14 @@ function usePanelNavigation() {
3379
3446
  }
3380
3447
 
3381
3448
  // src/hooks/usePanelFocus.ts
3382
- import { useState as useState18, useCallback as useCallback5 } from "react";
3449
+ import { useState as useState18, useCallback as useCallback6 } from "react";
3383
3450
  function usePanelFocus() {
3384
3451
  const [focused, setFocused] = useState18("sidebar");
3385
- const toggleFocus = useCallback5(() => {
3452
+ const toggleFocus = useCallback6(() => {
3386
3453
  setFocused((prev) => prev === "sidebar" ? "main" : "sidebar");
3387
3454
  }, []);
3388
- const focusSidebar = useCallback5(() => setFocused("sidebar"), []);
3389
- const focusMain = useCallback5(() => setFocused("main"), []);
3455
+ const focusSidebar = useCallback6(() => setFocused("sidebar"), []);
3456
+ const focusMain = useCallback6(() => setFocused("main"), []);
3390
3457
  return {
3391
3458
  focused,
3392
3459
  isSidebarFocused: focused === "sidebar",
@@ -3425,13 +3492,13 @@ function useSidebarItems() {
3425
3492
  }
3426
3493
 
3427
3494
  // src/hooks/useModal.ts
3428
- import { useState as useState19, useCallback as useCallback6 } from "react";
3495
+ import { useState as useState19, useCallback as useCallback7 } from "react";
3429
3496
  function useModal() {
3430
3497
  const [state, setState] = useState19(null);
3431
- const openModal = useCallback6((content, title) => {
3498
+ const openModal = useCallback7((content, title) => {
3432
3499
  setState({ content, title });
3433
3500
  }, []);
3434
- const closeModal = useCallback6(() => {
3501
+ const closeModal = useCallback7(() => {
3435
3502
  setState(null);
3436
3503
  }, []);
3437
3504
  return {
@@ -4183,6 +4250,7 @@ function AppPanel() {
4183
4250
  {
4184
4251
  args: nav.innerParams.args ?? [],
4185
4252
  tool: nav.innerParams.tool,
4253
+ interactive: nav.innerParams.interactive,
4186
4254
  onNavigate: nav.navigateInner,
4187
4255
  onBack: nav.goBackInner,
4188
4256
  width: w,
@@ -4198,6 +4266,7 @@ function AppPanel() {
4198
4266
  {
4199
4267
  args: nav.innerParams.args ?? [],
4200
4268
  tool: nav.innerParams.tool,
4269
+ interactive: nav.innerParams.interactive,
4201
4270
  onBack: nav.goBackInner,
4202
4271
  onHome: nav.goHomeInner,
4203
4272
  onExit: handleExit,
package/dist/mcp.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  resolveToolCommand,
18
18
  runCommand,
19
19
  savePipeline
20
- } from "./chunk-3XNNC5AW.js";
20
+ } from "./chunk-2B2UWOVQ.js";
21
21
 
22
22
  // src/mcp.ts
23
23
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polterware/polter",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "An interactive CLI for managing Supabase CLI workflows.",
5
5
  "type": "module",
6
6
  "bin": {