@polterware/polter 0.2.4 → 0.3.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.
@@ -673,12 +673,42 @@ var gitCommands = [
673
673
  { id: "git:merge", tool: "git", base: ["merge"], label: "merge", hint: "Merge branches" }
674
674
  ];
675
675
 
676
+ // src/data/commands/pkg.ts
677
+ var pkgCommands = [
678
+ // Build & Publish
679
+ { id: "pkg:build", tool: "pkg", base: ["run", "build"], label: "build", hint: "Run build script" },
680
+ { id: "pkg:publish", tool: "pkg", base: ["publish"], label: "publish", hint: "Publish package to registry" },
681
+ { id: "pkg:pack", tool: "pkg", base: ["pack"], label: "pack", hint: "Create tarball from package" },
682
+ { id: "pkg:version:patch", tool: "pkg", base: ["version", "patch"], label: "version patch", hint: "Bump patch version" },
683
+ { id: "pkg:version:minor", tool: "pkg", base: ["version", "minor"], label: "version minor", hint: "Bump minor version" },
684
+ { id: "pkg:version:major", tool: "pkg", base: ["version", "major"], label: "version major", hint: "Bump major version" },
685
+ { id: "pkg:login", tool: "pkg", base: ["login"], label: "login", hint: "Log in to registry" },
686
+ { id: "pkg:logout", tool: "pkg", base: ["logout"], label: "logout", hint: "Log out from registry" },
687
+ // Dependency Management
688
+ { id: "pkg:install", tool: "pkg", base: ["install"], label: "install", hint: "Install all dependencies" },
689
+ { id: "pkg:add", tool: "pkg", base: ["add"], label: "add", hint: "Add a dependency" },
690
+ { id: "pkg:remove", tool: "pkg", base: ["remove"], label: "remove", hint: "Remove a dependency" },
691
+ { id: "pkg:update", tool: "pkg", base: ["update"], label: "update", hint: "Update dependencies" },
692
+ { id: "pkg:outdated", tool: "pkg", base: ["outdated"], label: "outdated", hint: "List outdated packages" },
693
+ { id: "pkg:audit", tool: "pkg", base: ["audit"], label: "audit", hint: "Run security audit" },
694
+ { id: "pkg:ls", tool: "pkg", base: ["ls"], label: "ls", hint: "List installed packages" },
695
+ // Registry & Config
696
+ { id: "pkg:config:list", tool: "pkg", base: ["config", "list"], label: "config list", hint: "Show config" },
697
+ { id: "pkg:whoami", tool: "pkg", base: ["whoami"], label: "whoami", hint: "Show logged-in user" },
698
+ // Scripts & Info
699
+ { id: "pkg:run", tool: "pkg", base: ["run"], label: "run", hint: "Run a package script" },
700
+ { id: "pkg:info", tool: "pkg", base: ["info"], label: "info", hint: "Show package info" },
701
+ { id: "pkg:search", tool: "pkg", base: ["search"], label: "search", hint: "Search packages in registry" },
702
+ { id: "pkg:init", tool: "pkg", base: ["init"], label: "init", hint: "Initialize a new package.json" }
703
+ ];
704
+
676
705
  // src/data/commands/index.ts
677
706
  var allCommands = [
678
707
  ...supabaseCommands,
679
708
  ...ghCommands,
680
709
  ...vercelCommands,
681
- ...gitCommands
710
+ ...gitCommands,
711
+ ...pkgCommands
682
712
  ];
683
713
  var commandById = new Map(allCommands.map((cmd) => [cmd.id, cmd]));
684
714
  var commandByValue = new Map(
@@ -703,11 +733,10 @@ function findCommandByValue(value) {
703
733
  }
704
734
 
705
735
  // src/data/features.ts
736
+ var allSources = [...supabaseCommands, ...ghCommands, ...vercelCommands, ...gitCommands, ...pkgCommands];
706
737
  function pick(ids) {
707
738
  const idSet = new Set(ids);
708
- return [...supabaseCommands, ...ghCommands, ...vercelCommands, ...gitCommands].filter(
709
- (cmd) => idSet.has(cmd.id)
710
- );
739
+ return allSources.filter((cmd) => idSet.has(cmd.id));
711
740
  }
712
741
  var features = [
713
742
  {
@@ -746,7 +775,7 @@ var features = [
746
775
  },
747
776
  {
748
777
  id: "repo",
749
- icon: "\u{1F4E6}",
778
+ icon: "\u{1F5C3}\uFE0F",
750
779
  label: "Repo",
751
780
  commands: pick([
752
781
  "git:status",
@@ -822,6 +851,34 @@ var features = [
822
851
  "vercel:domains:rm"
823
852
  ])
824
853
  },
854
+ {
855
+ id: "packages",
856
+ icon: "\u{1F4CB}",
857
+ label: "Packages",
858
+ commands: pick([
859
+ "pkg:install",
860
+ "pkg:add",
861
+ "pkg:remove",
862
+ "pkg:update",
863
+ "pkg:outdated",
864
+ "pkg:audit",
865
+ "pkg:ls",
866
+ "pkg:build",
867
+ "pkg:run",
868
+ "pkg:publish",
869
+ "pkg:pack",
870
+ "pkg:version:patch",
871
+ "pkg:version:minor",
872
+ "pkg:version:major",
873
+ "pkg:login",
874
+ "pkg:logout",
875
+ "pkg:config:list",
876
+ "pkg:whoami",
877
+ "pkg:info",
878
+ "pkg:search",
879
+ "pkg:init"
880
+ ])
881
+ },
825
882
  {
826
883
  id: "setup",
827
884
  icon: "\u2699\uFE0F",
@@ -864,7 +921,12 @@ var toolFlags = {
864
921
  { value: "--yes", label: "--yes", hint: "Skip confirmation prompts" },
865
922
  { value: "--debug", label: "--debug", hint: "Debug mode" }
866
923
  ],
867
- git: []
924
+ git: [],
925
+ pkg: [
926
+ { value: "--save-dev", label: "--save-dev", hint: "Save as dev dependency" },
927
+ { value: "--global", label: "--global", hint: "Install globally" },
928
+ { value: "--dry-run", label: "--dry-run", hint: "Show what would happen" }
929
+ ]
868
930
  };
869
931
  function getFlagsForTool(toolId) {
870
932
  return toolFlags[toolId] ?? globalFlags;
@@ -923,20 +985,20 @@ function resolveSupabaseCommand(startDir = process.cwd(), env = process.env) {
923
985
  localBinDir
924
986
  };
925
987
  }
926
- async function runCommand(execution, args, cwd = process.cwd(), options) {
927
- return new Promise((resolve3) => {
928
- let stdout = "";
929
- let stderr = "";
930
- const resolvedExecution = typeof execution === "string" ? { command: execution } : execution;
931
- const child = spawn(resolvedExecution.command, args, {
932
- cwd,
933
- env: resolvedExecution.env,
934
- shell: true,
935
- stdio: [options?.quiet ? "pipe" : "inherit", "pipe", "pipe"]
936
- });
937
- if (options?.quiet) {
938
- child.stdin?.end();
939
- }
988
+ function runCommand(execution, args, cwd = process.cwd(), options) {
989
+ let stdout = "";
990
+ let stderr = "";
991
+ const resolvedExecution = typeof execution === "string" ? { command: execution } : execution;
992
+ const child = spawn(resolvedExecution.command, args, {
993
+ cwd,
994
+ env: resolvedExecution.env,
995
+ shell: true,
996
+ stdio: [options?.quiet ? "pipe" : "inherit", "pipe", "pipe"]
997
+ });
998
+ if (options?.quiet) {
999
+ child.stdin?.end();
1000
+ }
1001
+ const promise = new Promise((resolve3) => {
940
1002
  child.stdout?.on("data", (data) => {
941
1003
  const text = data.toString();
942
1004
  stdout += text;
@@ -960,6 +1022,10 @@ async function runCommand(execution, args, cwd = process.cwd(), options) {
960
1022
  resolve3({ exitCode: code, signal, stdout, stderr });
961
1023
  });
962
1024
  });
1025
+ return {
1026
+ promise,
1027
+ abort: () => child.kill("SIGTERM")
1028
+ };
963
1029
  }
964
1030
  function runInteractiveCommand(execution, args, cwd = process.cwd()) {
965
1031
  const resolved = typeof execution === "string" ? { command: execution } : execution;
@@ -978,12 +1044,126 @@ function runInteractiveCommand(execution, args, cwd = process.cwd()) {
978
1044
  };
979
1045
  }
980
1046
  async function runSupabaseCommand(args, cwd = process.cwd()) {
981
- return runCommand(resolveSupabaseCommand(cwd), args, cwd);
1047
+ return runCommand(resolveSupabaseCommand(cwd), args, cwd).promise;
1048
+ }
1049
+
1050
+ // src/lib/pkgManager.ts
1051
+ import { existsSync as existsSync2 } from "fs";
1052
+ import { join as join2, dirname as dirname2 } from "path";
1053
+ var LOCK_FILES = [
1054
+ { file: "bun.lockb", id: "bun" },
1055
+ { file: "bun.lock", id: "bun" },
1056
+ { file: "pnpm-lock.yaml", id: "pnpm" },
1057
+ { file: "yarn.lock", id: "yarn" },
1058
+ { file: "package-lock.json", id: "npm" }
1059
+ ];
1060
+ var MANAGER_META = {
1061
+ npm: { lockFile: "package-lock.json", command: "npm" },
1062
+ pnpm: { lockFile: "pnpm-lock.yaml", command: "pnpm" },
1063
+ yarn: { lockFile: "yarn.lock", command: "yarn" },
1064
+ bun: { lockFile: "bun.lockb", command: "bun" }
1065
+ };
1066
+ function detectPkgManager(cwd = process.cwd()) {
1067
+ let dir = cwd;
1068
+ const root = dirname2(dir) === dir ? dir : void 0;
1069
+ while (true) {
1070
+ for (const { file, id } of LOCK_FILES) {
1071
+ if (existsSync2(join2(dir, file))) {
1072
+ const meta = MANAGER_META[id];
1073
+ return { id, lockFile: meta.lockFile, command: meta.command };
1074
+ }
1075
+ }
1076
+ const parent = dirname2(dir);
1077
+ if (parent === dir || parent === root) break;
1078
+ dir = parent;
1079
+ }
1080
+ return { id: "npm", lockFile: "package-lock.json", command: "npm" };
1081
+ }
1082
+ var TRANSLATIONS = {
1083
+ "install": {},
1084
+ // same for all
1085
+ "add": {
1086
+ npm: ["install"]
1087
+ },
1088
+ "remove": {
1089
+ npm: ["uninstall"]
1090
+ },
1091
+ "run": {},
1092
+ // same for all
1093
+ "publish": {
1094
+ bun: null
1095
+ },
1096
+ "audit": {
1097
+ bun: null
1098
+ },
1099
+ "outdated": {
1100
+ bun: null
1101
+ },
1102
+ "ls": {
1103
+ yarn: ["list"],
1104
+ bun: null
1105
+ },
1106
+ "pack": {
1107
+ bun: null
1108
+ },
1109
+ "version": {},
1110
+ // same for all
1111
+ "login": {
1112
+ bun: null
1113
+ },
1114
+ "logout": {
1115
+ bun: null
1116
+ },
1117
+ "config": {},
1118
+ // same for all
1119
+ "whoami": {
1120
+ bun: null
1121
+ },
1122
+ "info": {
1123
+ npm: ["info"],
1124
+ pnpm: ["info"],
1125
+ yarn: ["info"],
1126
+ bun: null
1127
+ },
1128
+ "search": {
1129
+ pnpm: null,
1130
+ yarn: null,
1131
+ bun: null
1132
+ },
1133
+ "init": {}
1134
+ // same for all
1135
+ };
1136
+ var UnsupportedCommandError = class extends Error {
1137
+ constructor(command, manager) {
1138
+ super(`"${command}" is not supported by ${manager}`);
1139
+ this.name = "UnsupportedCommandError";
1140
+ }
1141
+ };
1142
+ function translateCommand(base, manager) {
1143
+ const [subcommand, ...rest] = base;
1144
+ if (!subcommand) {
1145
+ return { command: manager, args: [] };
1146
+ }
1147
+ const translation = TRANSLATIONS[subcommand];
1148
+ if (translation) {
1149
+ const managerEntry = translation[manager];
1150
+ if (managerEntry === null) {
1151
+ throw new UnsupportedCommandError(subcommand, manager);
1152
+ }
1153
+ if (managerEntry !== void 0) {
1154
+ return { command: manager, args: [...managerEntry, ...rest] };
1155
+ }
1156
+ }
1157
+ return { command: manager, args: [subcommand, ...rest] };
1158
+ }
1159
+ function resolvePkgArgs(base, cwd = process.cwd()) {
1160
+ const mgr = detectPkgManager(cwd);
1161
+ return translateCommand(base, mgr.id);
982
1162
  }
983
1163
 
984
1164
  // src/lib/toolResolver.ts
985
- import { existsSync as existsSync2, readFileSync } from "fs";
986
- import { join as join2 } from "path";
1165
+ import { existsSync as existsSync3, readFileSync } from "fs";
1166
+ import { join as join3 } from "path";
987
1167
 
988
1168
  // src/lib/system.ts
989
1169
  import { execSync } from "child_process";
@@ -1012,6 +1192,13 @@ function resolveToolCommand(toolId, cwd = process.cwd()) {
1012
1192
  source: resolved.source
1013
1193
  };
1014
1194
  }
1195
+ if (toolId === "pkg") {
1196
+ const mgr = detectPkgManager(cwd);
1197
+ if (!commandExists(mgr.command)) {
1198
+ return { command: mgr.command, source: "not-found" };
1199
+ }
1200
+ return { command: mgr.command, env: { ...process.env }, source: "path" };
1201
+ }
1015
1202
  const command = toolId;
1016
1203
  if (!commandExists(command)) {
1017
1204
  return { command, source: "not-found" };
@@ -1029,6 +1216,10 @@ function getToolVersion(toolId) {
1029
1216
  return execCapture("vercel --version").split("\n")[0]?.trim();
1030
1217
  case "git":
1031
1218
  return execCapture("git --version").replace(/^git version\s+/i, "").trim();
1219
+ case "pkg": {
1220
+ const mgr = detectPkgManager();
1221
+ return execCapture(`${mgr.command} --version`).split("\n")[0]?.trim();
1222
+ }
1032
1223
  default:
1033
1224
  return void 0;
1034
1225
  }
@@ -1044,9 +1235,11 @@ function getToolInfo(toolId) {
1044
1235
  supabase: "Supabase CLI",
1045
1236
  gh: "GitHub CLI",
1046
1237
  vercel: "Vercel CLI",
1047
- git: "Git"
1238
+ git: "Git",
1239
+ pkg: "Package Manager"
1048
1240
  };
1049
- const installed = commandExists(toolId === "supabase" ? "supabase" : toolId);
1241
+ const commandName = toolId === "supabase" ? "supabase" : toolId === "pkg" ? detectPkgManager().command : toolId;
1242
+ const installed = commandExists(commandName);
1050
1243
  const version = installed ? getToolVersion(toolId) : void 0;
1051
1244
  const info = {
1052
1245
  id: toolId,
@@ -1059,8 +1252,8 @@ function getToolInfo(toolId) {
1059
1252
  }
1060
1253
  function detectSupabaseLink(cwd) {
1061
1254
  try {
1062
- const configPath = join2(cwd, ".supabase", "config.toml");
1063
- if (existsSync2(configPath)) {
1255
+ const configPath = join3(cwd, ".supabase", "config.toml");
1256
+ if (existsSync3(configPath)) {
1064
1257
  const content = readFileSync(configPath, "utf-8");
1065
1258
  const match = content.match(/project_id\s*=\s*"([^"]+)"/);
1066
1259
  return { linked: true, project: match?.[1] };
@@ -1071,8 +1264,8 @@ function detectSupabaseLink(cwd) {
1071
1264
  }
1072
1265
  function detectVercelLink(cwd) {
1073
1266
  try {
1074
- const projectPath = join2(cwd, ".vercel", "project.json");
1075
- if (existsSync2(projectPath)) {
1267
+ const projectPath = join3(cwd, ".vercel", "project.json");
1268
+ if (existsSync3(projectPath)) {
1076
1269
  const data = JSON.parse(readFileSync(projectPath, "utf-8"));
1077
1270
  return { linked: true, project: data.projectId };
1078
1271
  }
@@ -1080,6 +1273,18 @@ function detectVercelLink(cwd) {
1080
1273
  }
1081
1274
  return { linked: false };
1082
1275
  }
1276
+ function detectPkgLink(cwd) {
1277
+ try {
1278
+ const pkgPath = join3(cwd, "package.json");
1279
+ if (existsSync3(pkgPath)) {
1280
+ const data = JSON.parse(readFileSync(pkgPath, "utf-8"));
1281
+ const mgr = detectPkgManager(cwd);
1282
+ return { linked: true, project: data.name ? `${data.name} (${mgr.id})` : mgr.id };
1283
+ }
1284
+ } catch {
1285
+ }
1286
+ return { linked: false };
1287
+ }
1083
1288
  function detectGhLink(cwd) {
1084
1289
  try {
1085
1290
  const remote = execCapture(`git -C "${cwd}" remote get-url origin 2>/dev/null`);
@@ -1108,6 +1313,9 @@ function getToolLinkInfo(toolId, cwd = process.cwd()) {
1108
1313
  case "gh":
1109
1314
  linkStatus = detectGhLink(cwd);
1110
1315
  break;
1316
+ case "pkg":
1317
+ linkStatus = detectPkgLink(cwd);
1318
+ break;
1111
1319
  }
1112
1320
  }
1113
1321
  const info = { ...base, ...linkStatus };
@@ -1115,6 +1323,194 @@ function getToolLinkInfo(toolId, cwd = process.cwd()) {
1115
1323
  return info;
1116
1324
  }
1117
1325
 
1326
+ // src/lib/processManager.ts
1327
+ import { spawn as spawn2 } from "child_process";
1328
+ var DEFAULT_BUFFER_CAP = 1e3;
1329
+ function createRingBuffer(cap = DEFAULT_BUFFER_CAP) {
1330
+ return { lines: [], cap, totalLines: 0 };
1331
+ }
1332
+ function appendToBuffer(buf, data) {
1333
+ const newLines = data.split("\n");
1334
+ if (newLines.length > 0 && newLines[newLines.length - 1] === "") {
1335
+ newLines.pop();
1336
+ }
1337
+ if (newLines.length === 0) return;
1338
+ buf.lines.push(...newLines);
1339
+ buf.totalLines += newLines.length;
1340
+ if (buf.lines.length > buf.cap) {
1341
+ buf.lines.splice(0, buf.lines.length - buf.cap);
1342
+ }
1343
+ }
1344
+ function tailBuffer(buf, n) {
1345
+ if (n === void 0 || n >= buf.lines.length) return [...buf.lines];
1346
+ return buf.lines.slice(-n);
1347
+ }
1348
+ var registry = /* @__PURE__ */ new Map();
1349
+ var cleanupRegistered = false;
1350
+ function registerCleanup() {
1351
+ if (cleanupRegistered) return;
1352
+ cleanupRegistered = true;
1353
+ const cleanup = () => {
1354
+ for (const proc of registry.values()) {
1355
+ if (proc.status === "running" && proc.child.pid) {
1356
+ try {
1357
+ process.kill(-proc.child.pid, "SIGKILL");
1358
+ } catch {
1359
+ }
1360
+ }
1361
+ }
1362
+ };
1363
+ process.on("exit", cleanup);
1364
+ process.on("SIGINT", () => {
1365
+ cleanup();
1366
+ process.exit(130);
1367
+ });
1368
+ process.on("SIGTERM", () => {
1369
+ cleanup();
1370
+ process.exit(143);
1371
+ });
1372
+ }
1373
+ function generateProcessId(command, args) {
1374
+ const slug = [command, ...args].join("-").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
1375
+ if (!registry.has(slug)) return slug;
1376
+ let i = 2;
1377
+ while (registry.has(`${slug}-${i}`)) i++;
1378
+ return `${slug}-${i}`;
1379
+ }
1380
+ function startProcess(id, command, args = [], cwd = process.cwd(), env) {
1381
+ const existing = registry.get(id);
1382
+ if (existing && existing.status === "running") {
1383
+ throw new Error(`Process "${id}" is already running (pid ${existing.child.pid})`);
1384
+ }
1385
+ registerCleanup();
1386
+ const child = spawn2(command, args, {
1387
+ cwd,
1388
+ env: env ?? process.env,
1389
+ detached: true,
1390
+ stdio: ["ignore", "pipe", "pipe"],
1391
+ shell: true
1392
+ });
1393
+ const tracked = {
1394
+ id,
1395
+ command,
1396
+ args,
1397
+ cwd,
1398
+ child,
1399
+ status: "running",
1400
+ exitCode: null,
1401
+ signal: null,
1402
+ startedAt: /* @__PURE__ */ new Date(),
1403
+ exitedAt: null,
1404
+ stdout: createRingBuffer(),
1405
+ stderr: createRingBuffer()
1406
+ };
1407
+ child.stdout?.on("data", (data) => {
1408
+ appendToBuffer(tracked.stdout, data.toString());
1409
+ });
1410
+ child.stderr?.on("data", (data) => {
1411
+ appendToBuffer(tracked.stderr, data.toString());
1412
+ });
1413
+ child.on("exit", (code, signal) => {
1414
+ tracked.status = "exited";
1415
+ tracked.exitCode = code;
1416
+ tracked.signal = signal;
1417
+ tracked.exitedAt = /* @__PURE__ */ new Date();
1418
+ });
1419
+ child.on("error", (err) => {
1420
+ tracked.status = "errored";
1421
+ tracked.exitedAt = /* @__PURE__ */ new Date();
1422
+ appendToBuffer(tracked.stderr, `spawn error: ${err.message}
1423
+ `);
1424
+ });
1425
+ registry.set(id, tracked);
1426
+ return toProcessInfo(tracked);
1427
+ }
1428
+ function stopProcess(id) {
1429
+ const proc = registry.get(id);
1430
+ if (!proc) throw new Error(`No tracked process with id "${id}"`);
1431
+ if (proc.status !== "running") return Promise.resolve(toProcessInfo(proc));
1432
+ return new Promise((resolve3) => {
1433
+ const escalateTimer = setTimeout(() => {
1434
+ if (proc.status === "running" && proc.child.pid) {
1435
+ try {
1436
+ process.kill(-proc.child.pid, "SIGKILL");
1437
+ } catch {
1438
+ }
1439
+ }
1440
+ }, 5e3);
1441
+ const onDone = () => {
1442
+ clearTimeout(escalateTimer);
1443
+ resolve3(toProcessInfo(proc));
1444
+ };
1445
+ proc.child.once("exit", onDone);
1446
+ if (proc.child.pid) {
1447
+ try {
1448
+ process.kill(-proc.child.pid, "SIGTERM");
1449
+ } catch {
1450
+ }
1451
+ }
1452
+ if (proc.status !== "running") {
1453
+ clearTimeout(escalateTimer);
1454
+ proc.child.removeListener("exit", onDone);
1455
+ resolve3(toProcessInfo(proc));
1456
+ }
1457
+ });
1458
+ }
1459
+ function listProcesses() {
1460
+ return Array.from(registry.values()).map(toProcessInfo);
1461
+ }
1462
+ function getProcessOutput(id, tail, stream) {
1463
+ const proc = registry.get(id);
1464
+ if (!proc) throw new Error(`No tracked process with id "${id}"`);
1465
+ const which = stream ?? "both";
1466
+ return {
1467
+ stdout: which === "stderr" ? [] : tailBuffer(proc.stdout, tail),
1468
+ stderr: which === "stdout" ? [] : tailBuffer(proc.stderr, tail),
1469
+ stdoutLineCount: proc.stdout.totalLines,
1470
+ stderrLineCount: proc.stderr.totalLines
1471
+ };
1472
+ }
1473
+ function isProcessRunning(id) {
1474
+ const proc = registry.get(id);
1475
+ if (!proc) return false;
1476
+ if (proc.status !== "running") return false;
1477
+ if (proc.child.pid) {
1478
+ try {
1479
+ process.kill(proc.child.pid, 0);
1480
+ return true;
1481
+ } catch {
1482
+ return false;
1483
+ }
1484
+ }
1485
+ return false;
1486
+ }
1487
+ function removeProcess(id) {
1488
+ const proc = registry.get(id);
1489
+ if (!proc) throw new Error(`No tracked process with id "${id}"`);
1490
+ if (proc.status === "running") {
1491
+ throw new Error(`Cannot remove running process "${id}". Stop it first.`);
1492
+ }
1493
+ registry.delete(id);
1494
+ }
1495
+ function toProcessInfo(proc) {
1496
+ const now = Date.now();
1497
+ const start = proc.startedAt.getTime();
1498
+ const end = proc.exitedAt ? proc.exitedAt.getTime() : now;
1499
+ return {
1500
+ id: proc.id,
1501
+ command: proc.command,
1502
+ args: proc.args,
1503
+ cwd: proc.cwd,
1504
+ pid: proc.child.pid,
1505
+ status: proc.status,
1506
+ exitCode: proc.exitCode,
1507
+ signal: proc.signal,
1508
+ startedAt: proc.startedAt.toISOString(),
1509
+ exitedAt: proc.exitedAt?.toISOString() ?? null,
1510
+ uptime: end - start
1511
+ };
1512
+ }
1513
+
1118
1514
  // src/pipeline/engine.ts
1119
1515
  async function executePipeline(pipeline, onProgress, cwd = process.cwd()) {
1120
1516
  const stepResults = pipeline.steps.map((step) => ({
@@ -1134,13 +1530,20 @@ async function executePipeline(pipeline, onProgress, cwd = process.cwd()) {
1134
1530
  const cmdDef = getCommandById(step.commandId);
1135
1531
  const toolId = cmdDef?.tool ?? "supabase";
1136
1532
  const resolved = resolveToolCommand(toolId, cwd);
1137
- const baseArgs = cmdDef?.base ?? [];
1533
+ let baseArgs = cmdDef?.base ?? [];
1534
+ if (toolId === "pkg" && cmdDef) {
1535
+ try {
1536
+ const translated = resolvePkgArgs(cmdDef.base, cwd);
1537
+ baseArgs = translated.args;
1538
+ } catch {
1539
+ }
1540
+ }
1138
1541
  const allArgs = [...baseArgs, ...step.args, ...step.flags];
1139
1542
  const result = await runCommand(
1140
1543
  { command: resolved.command, env: resolved.env },
1141
1544
  allArgs,
1142
1545
  cwd
1143
- );
1546
+ ).promise;
1144
1547
  const success = !result.spawnError && result.exitCode === 0;
1145
1548
  stepResults[i] = {
1146
1549
  step,
@@ -1161,23 +1564,23 @@ async function executePipeline(pipeline, onProgress, cwd = process.cwd()) {
1161
1564
  }
1162
1565
 
1163
1566
  // src/config/projectConfig.ts
1164
- import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
1165
- import { join as join4 } from "path";
1567
+ import { existsSync as existsSync5, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
1568
+ import { join as join5 } from "path";
1166
1569
 
1167
1570
  // src/lib/packageRoot.ts
1168
- import { existsSync as existsSync3 } from "fs";
1169
- import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
1571
+ import { existsSync as existsSync4 } from "fs";
1572
+ import { dirname as dirname3, join as join4, resolve as resolve2 } from "path";
1170
1573
  var rootCache = /* @__PURE__ */ new Map();
1171
1574
  function findNearestPackageRoot(startDir = process.cwd()) {
1172
1575
  const resolvedStart = resolve2(startDir);
1173
1576
  if (rootCache.has(resolvedStart)) return rootCache.get(resolvedStart);
1174
1577
  let currentDir = resolvedStart;
1175
1578
  while (true) {
1176
- if (existsSync3(join3(currentDir, "package.json"))) {
1579
+ if (existsSync4(join4(currentDir, "package.json"))) {
1177
1580
  rootCache.set(resolvedStart, currentDir);
1178
1581
  return currentDir;
1179
1582
  }
1180
- const parentDir = dirname2(currentDir);
1583
+ const parentDir = dirname3(currentDir);
1181
1584
  if (parentDir === currentDir) {
1182
1585
  rootCache.set(resolvedStart, void 0);
1183
1586
  return void 0;
@@ -1199,13 +1602,13 @@ function defaultConfig() {
1199
1602
  function getProjectConfigPath(startDir) {
1200
1603
  const root = findNearestPackageRoot(startDir);
1201
1604
  if (!root) return void 0;
1202
- const dir = join4(root, CONFIG_DIR);
1203
- return { dir, file: join4(dir, CONFIG_FILE) };
1605
+ const dir = join5(root, CONFIG_DIR);
1606
+ return { dir, file: join5(dir, CONFIG_FILE) };
1204
1607
  }
1205
1608
  function readProjectConfig(startDir) {
1206
1609
  const paths = getProjectConfigPath(startDir);
1207
1610
  if (!paths) return void 0;
1208
- if (!existsSync4(paths.file)) return void 0;
1611
+ if (!existsSync5(paths.file)) return void 0;
1209
1612
  try {
1210
1613
  const raw = readFileSync2(paths.file, "utf-8");
1211
1614
  return JSON.parse(raw);
@@ -1306,8 +1709,8 @@ function findPipelineByName(name, startDir) {
1306
1709
  }
1307
1710
 
1308
1711
  // src/declarative/parser.ts
1309
- import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
1310
- import { join as join5 } from "path";
1712
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
1713
+ import { join as join6 } from "path";
1311
1714
  var YAML_FILE = "polter.yaml";
1312
1715
  function parseSimpleYaml(content) {
1313
1716
  const lines = content.split("\n");
@@ -1379,8 +1782,8 @@ function parseValue(raw) {
1379
1782
  return raw;
1380
1783
  }
1381
1784
  function findPolterYaml(startDir = process.cwd()) {
1382
- const filePath = join5(startDir, YAML_FILE);
1383
- return existsSync5(filePath) ? filePath : void 0;
1785
+ const filePath = join6(startDir, YAML_FILE);
1786
+ return existsSync6(filePath) ? filePath : void 0;
1384
1787
  }
1385
1788
  function parsePolterYaml(startDir = process.cwd()) {
1386
1789
  const filePath = findPolterYaml(startDir);
@@ -1517,13 +1920,258 @@ async function applyActions(actions, cwd = process.cwd(), onProgress) {
1517
1920
  { command: resolved.command, env: resolved.env },
1518
1921
  action.args,
1519
1922
  cwd
1520
- );
1923
+ ).promise;
1521
1924
  const success = !result.spawnError && result.exitCode === 0;
1522
1925
  results.push({ action, success, result });
1523
1926
  }
1524
1927
  return results;
1525
1928
  }
1526
1929
 
1930
+ // src/lib/mcpInstaller.ts
1931
+ import { spawnSync as spawnSync2 } from "child_process";
1932
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, existsSync as existsSync7 } from "fs";
1933
+ import { join as join7, dirname as dirname4 } from "path";
1934
+ import { fileURLToPath } from "url";
1935
+ import { homedir } from "os";
1936
+ import pc from "picocolors";
1937
+ var __dirname = dirname4(fileURLToPath(import.meta.url));
1938
+ function readPkgVersion() {
1939
+ for (const rel of ["../../package.json", "../package.json"]) {
1940
+ const p = join7(__dirname, rel);
1941
+ if (existsSync7(p)) {
1942
+ const pkg = JSON.parse(readFileSync4(p, "utf-8"));
1943
+ return pkg.version;
1944
+ }
1945
+ }
1946
+ return "0.0.0";
1947
+ }
1948
+ var MCP_ARGS = ["npx", "-y", "-p", "@polterware/polter@latest", "polter-mcp"];
1949
+ var SCOPE_LABELS = {
1950
+ local: "local (this machine)",
1951
+ project: "project (shared via repo)",
1952
+ user: "global (all projects)"
1953
+ };
1954
+ function tryClaudeCli(scope) {
1955
+ if (!commandExists("claude")) return false;
1956
+ const result = spawnSync2("claude", ["mcp", "add", "-s", scope, "polter", "--", ...MCP_ARGS], {
1957
+ stdio: "inherit",
1958
+ shell: true
1959
+ });
1960
+ return result.status === 0;
1961
+ }
1962
+ function getSettingsPath(scope) {
1963
+ if (scope === "project") {
1964
+ return join7(process.cwd(), ".mcp.json");
1965
+ }
1966
+ return join7(homedir(), ".claude", "settings.json");
1967
+ }
1968
+ function tryManualInstall(scope) {
1969
+ const settingsPath = getSettingsPath(scope);
1970
+ const dir = join7(settingsPath, "..");
1971
+ let settings = {};
1972
+ if (existsSync7(settingsPath)) {
1973
+ try {
1974
+ settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
1975
+ } catch {
1976
+ process.stderr.write(pc.red(`Failed to parse ${settingsPath}
1977
+ `));
1978
+ return false;
1979
+ }
1980
+ } else {
1981
+ mkdirSync2(dir, { recursive: true });
1982
+ }
1983
+ const mcpServers = settings.mcpServers ?? {};
1984
+ mcpServers.polter = {
1985
+ command: "npx",
1986
+ args: ["-y", "-p", "@polterware/polter@latest", "polter-mcp"]
1987
+ };
1988
+ settings.mcpServers = mcpServers;
1989
+ writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1990
+ return true;
1991
+ }
1992
+ async function installMcpServer(scope) {
1993
+ process.stdout.write(pc.bold(`Installing Polter MCP server \u2014 ${SCOPE_LABELS[scope]}
1994
+
1995
+ `));
1996
+ if (commandExists("claude")) {
1997
+ process.stdout.write(` Using 'claude mcp add -s ${scope}'...
1998
+ `);
1999
+ if (tryClaudeCli(scope)) {
2000
+ process.stdout.write(pc.green("\n Done! Restart Claude Code to use Polter tools.\n"));
2001
+ return;
2002
+ }
2003
+ process.stdout.write(pc.yellow(" 'claude mcp add' failed, falling back to manual install...\n\n"));
2004
+ }
2005
+ const settingsPath = getSettingsPath(scope);
2006
+ process.stdout.write(` Writing to ${settingsPath}...
2007
+ `);
2008
+ if (tryManualInstall(scope)) {
2009
+ process.stdout.write(pc.green("\n Done! Restart Claude Code to use Polter tools.\n"));
2010
+ } else {
2011
+ process.stderr.write(pc.red("\n Failed to install. Add manually:\n\n"));
2012
+ process.stderr.write(` ${pc.dim(JSON.stringify({ mcpServers: { polter: { command: "npx", args: ["-y", "-p", "@polterware/polter@latest", "polter-mcp"] } } }, null, 2))}
2013
+ `);
2014
+ process.exit(1);
2015
+ }
2016
+ }
2017
+ async function removeMcpServer(scope) {
2018
+ process.stdout.write(pc.bold(`Removing Polter MCP server \u2014 ${SCOPE_LABELS[scope]}
2019
+
2020
+ `));
2021
+ if (commandExists("claude")) {
2022
+ const result = spawnSync2("claude", ["mcp", "remove", "-s", scope, "polter"], {
2023
+ stdio: "inherit",
2024
+ shell: true
2025
+ });
2026
+ if (result.status === 0) {
2027
+ process.stdout.write(pc.green("\n Done! Polter MCP server removed.\n"));
2028
+ return;
2029
+ }
2030
+ process.stdout.write(pc.yellow(" 'claude mcp remove' failed, falling back to manual removal...\n\n"));
2031
+ }
2032
+ const settingsPath = getSettingsPath(scope);
2033
+ if (!existsSync7(settingsPath)) {
2034
+ process.stdout.write(pc.yellow(" No settings file found. Nothing to remove.\n"));
2035
+ return;
2036
+ }
2037
+ let settings;
2038
+ try {
2039
+ settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
2040
+ } catch {
2041
+ process.stderr.write(pc.red(` Failed to parse ${settingsPath}
2042
+ `));
2043
+ process.exit(1);
2044
+ return;
2045
+ }
2046
+ const mcpServers = settings.mcpServers;
2047
+ if (!mcpServers?.polter) {
2048
+ process.stdout.write(pc.yellow(" Polter MCP server not found in settings. Nothing to remove.\n"));
2049
+ return;
2050
+ }
2051
+ delete mcpServers.polter;
2052
+ settings.mcpServers = mcpServers;
2053
+ writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
2054
+ process.stdout.write(pc.green(" Done! Polter MCP server removed.\n"));
2055
+ }
2056
+ function getMcpStatusInfo() {
2057
+ const version = readPkgVersion();
2058
+ const scopeDefs = [
2059
+ { label: "Project (.mcp.json)", path: join7(process.cwd(), ".mcp.json"), scope: "project" },
2060
+ { label: "User (~/.claude/settings.json)", path: join7(homedir(), ".claude", "settings.json"), scope: "user" }
2061
+ ];
2062
+ const scopes = scopeDefs.map((s) => {
2063
+ if (!existsSync7(s.path)) {
2064
+ return { label: s.label, scope: s.scope, registered: false };
2065
+ }
2066
+ try {
2067
+ const settings = JSON.parse(readFileSync4(s.path, "utf-8"));
2068
+ const mcpServers = settings.mcpServers;
2069
+ return { label: s.label, scope: s.scope, registered: !!mcpServers?.polter };
2070
+ } catch {
2071
+ return { label: s.label, scope: s.scope, registered: false, error: "error reading file" };
2072
+ }
2073
+ });
2074
+ return { installedVersion: version, latestVersion: null, scopes };
2075
+ }
2076
+ async function installMcpServerSilent(scope) {
2077
+ const messages = [];
2078
+ messages.push(`Installing Polter MCP server \u2014 ${SCOPE_LABELS[scope]}`);
2079
+ if (commandExists("claude")) {
2080
+ messages.push(`Using 'claude mcp add -s ${scope}'...`);
2081
+ if (tryClaudeCli(scope)) {
2082
+ messages.push("Done! Restart Claude Code to use Polter tools.");
2083
+ return { success: true, message: messages.join("\n") };
2084
+ }
2085
+ messages.push("'claude mcp add' failed, falling back to manual install...");
2086
+ }
2087
+ const settingsPath = getSettingsPath(scope);
2088
+ messages.push(`Writing to ${settingsPath}...`);
2089
+ if (tryManualInstall(scope)) {
2090
+ messages.push("Done! Restart Claude Code to use Polter tools.");
2091
+ return { success: true, message: messages.join("\n") };
2092
+ }
2093
+ return { success: false, message: "Failed to install. Try manual installation." };
2094
+ }
2095
+ async function removeMcpServerSilent(scope) {
2096
+ const messages = [];
2097
+ messages.push(`Removing Polter MCP server \u2014 ${SCOPE_LABELS[scope]}`);
2098
+ if (commandExists("claude")) {
2099
+ const result = spawnSync2("claude", ["mcp", "remove", "-s", scope, "polter"], {
2100
+ encoding: "utf-8",
2101
+ shell: true
2102
+ });
2103
+ if (result.status === 0) {
2104
+ messages.push("Done! Polter MCP server removed.");
2105
+ return { success: true, message: messages.join("\n") };
2106
+ }
2107
+ messages.push("'claude mcp remove' failed, falling back to manual removal...");
2108
+ }
2109
+ const settingsPath = getSettingsPath(scope);
2110
+ if (!existsSync7(settingsPath)) {
2111
+ messages.push("No settings file found. Nothing to remove.");
2112
+ return { success: true, message: messages.join("\n") };
2113
+ }
2114
+ let settings;
2115
+ try {
2116
+ settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
2117
+ } catch {
2118
+ return { success: false, message: `Failed to parse ${settingsPath}` };
2119
+ }
2120
+ const mcpServers = settings.mcpServers;
2121
+ if (!mcpServers?.polter) {
2122
+ messages.push("Polter MCP server not found in settings. Nothing to remove.");
2123
+ return { success: true, message: messages.join("\n") };
2124
+ }
2125
+ delete mcpServers.polter;
2126
+ settings.mcpServers = mcpServers;
2127
+ writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n");
2128
+ messages.push("Done! Polter MCP server removed.");
2129
+ return { success: true, message: messages.join("\n") };
2130
+ }
2131
+ async function mcpStatus() {
2132
+ process.stdout.write(pc.bold("Polter MCP Server Status\n\n"));
2133
+ const pkgVersion = readPkgVersion();
2134
+ process.stdout.write(` Installed version: ${pc.cyan(pkgVersion)}
2135
+ `);
2136
+ const latestResult = spawnSync2("npm", ["view", "@polterware/polter", "version"], {
2137
+ encoding: "utf-8",
2138
+ shell: true,
2139
+ timeout: 1e4
2140
+ });
2141
+ const latest = latestResult.stdout?.trim();
2142
+ if (latest) {
2143
+ const upToDate = latest === pkgVersion;
2144
+ process.stdout.write(` Latest version: ${upToDate ? pc.green(latest) : pc.yellow(`${latest} (update available)`)}
2145
+ `);
2146
+ }
2147
+ process.stdout.write("\n");
2148
+ const scopes = [
2149
+ { label: "Project (.mcp.json)", path: join7(process.cwd(), ".mcp.json"), key: "project" },
2150
+ { label: "User (~/.claude/settings.json)", path: join7(homedir(), ".claude", "settings.json"), key: "user" }
2151
+ ];
2152
+ for (const scope of scopes) {
2153
+ if (!existsSync7(scope.path)) {
2154
+ process.stdout.write(` ${scope.label}: ${pc.dim("not found")}
2155
+ `);
2156
+ continue;
2157
+ }
2158
+ try {
2159
+ const settings = JSON.parse(readFileSync4(scope.path, "utf-8"));
2160
+ const mcpServers = settings.mcpServers;
2161
+ if (mcpServers?.polter) {
2162
+ process.stdout.write(` ${scope.label}: ${pc.green("registered")}
2163
+ `);
2164
+ } else {
2165
+ process.stdout.write(` ${scope.label}: ${pc.dim("not registered")}
2166
+ `);
2167
+ }
2168
+ } catch {
2169
+ process.stdout.write(` ${scope.label}: ${pc.red("error reading file")}
2170
+ `);
2171
+ }
2172
+ }
2173
+ }
2174
+
1527
2175
  export {
1528
2176
  __export,
1529
2177
  allCommands,
@@ -1539,9 +2187,19 @@ export {
1539
2187
  runInteractiveCommand,
1540
2188
  runSupabaseCommand,
1541
2189
  commandExists,
2190
+ detectPkgManager,
2191
+ translateCommand,
2192
+ resolvePkgArgs,
1542
2193
  resolveToolCommand,
1543
2194
  getToolInfo,
1544
2195
  getToolLinkInfo,
2196
+ generateProcessId,
2197
+ startProcess,
2198
+ stopProcess,
2199
+ listProcesses,
2200
+ getProcessOutput,
2201
+ isProcessRunning,
2202
+ removeProcess,
1545
2203
  executePipeline,
1546
2204
  findNearestPackageRoot,
1547
2205
  getProjectConfigPath,
@@ -1551,8 +2209,15 @@ export {
1551
2209
  savePipeline,
1552
2210
  deletePipeline,
1553
2211
  findPipelineByName,
2212
+ findPolterYaml,
1554
2213
  parsePolterYaml,
1555
2214
  getCurrentStatus,
1556
2215
  planChanges,
1557
- applyActions
2216
+ applyActions,
2217
+ installMcpServer,
2218
+ removeMcpServer,
2219
+ getMcpStatusInfo,
2220
+ installMcpServerSilent,
2221
+ removeMcpServerSilent,
2222
+ mcpStatus
1558
2223
  };