@waniwani/cli 0.0.42 → 0.0.45

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/index.js CHANGED
@@ -762,7 +762,7 @@ var loginCommand = new Command3("login").description("Log in to WaniWani").optio
762
762
 
763
763
  // src/commands/logout.ts
764
764
  import { Command as Command4 } from "commander";
765
- var logoutCommand = new Command4("logout").description("Log out from WaniWani").action(async (_, command) => {
765
+ var logoutCommand = new Command4("logout").description("Log out from WaniWani").action(async (_options, command) => {
766
766
  const globalOptions = command.optsWithGlobals();
767
767
  const json = globalOptions.json ?? false;
768
768
  try {
@@ -790,10 +790,10 @@ var logoutCommand = new Command4("logout").description("Log out from WaniWani").
790
790
  import { Command as Command21 } from "commander";
791
791
 
792
792
  // src/commands/mcp/clone.ts
793
- import { execSync } from "child_process";
793
+ import { execFileSync as execFileSync2, execSync } from "child_process";
794
794
  import { existsSync as existsSync3 } from "fs";
795
795
  import { readFile as readFile2 } from "fs/promises";
796
- import { join as join3 } from "path";
796
+ import { join as join4 } from "path";
797
797
  import { Command as Command5 } from "commander";
798
798
  import ora2 from "ora";
799
799
 
@@ -885,9 +885,132 @@ var api = {
885
885
  getBaseUrl: () => config.getApiUrl()
886
886
  };
887
887
 
888
+ // src/lib/git-auth.ts
889
+ import { execFileSync } from "child_process";
890
+ import { chmodSync, mkdtempSync, rmSync, writeFileSync } from "fs";
891
+ import { tmpdir } from "os";
892
+ import { join as join3 } from "path";
893
+ function getGitHubApiBaseUrl(remoteUrl) {
894
+ try {
895
+ const parsed = new URL(remoteUrl);
896
+ return parsed.hostname === "github.com" || parsed.hostname === "www.github.com" ? "https://api.github.com" : `${parsed.protocol}//${parsed.host}/api/v3`;
897
+ } catch {
898
+ return null;
899
+ }
900
+ }
901
+ function parseCloneUrlAuth(cloneUrl) {
902
+ try {
903
+ const parsed = new URL(cloneUrl);
904
+ const username = decodeURIComponent(parsed.username);
905
+ const password = decodeURIComponent(parsed.password);
906
+ if (username && password) {
907
+ parsed.username = "";
908
+ parsed.password = "";
909
+ const remoteUrl = parsed.toString();
910
+ return {
911
+ cloneUrl,
912
+ remoteUrl,
913
+ credentials: { username, password },
914
+ githubApiBaseUrl: getGitHubApiBaseUrl(remoteUrl)
915
+ };
916
+ }
917
+ } catch {
918
+ }
919
+ return {
920
+ cloneUrl,
921
+ remoteUrl: cloneUrl,
922
+ credentials: null,
923
+ githubApiBaseUrl: null
924
+ };
925
+ }
926
+ async function getGitAuthContext(mcpId) {
927
+ try {
928
+ const gitAuth = await api.get(
929
+ `/api/mcp/repositories/${mcpId}/git-auth`
930
+ );
931
+ const parsedRemote = new URL(gitAuth.remoteUrl);
932
+ parsedRemote.username = gitAuth.username;
933
+ parsedRemote.password = gitAuth.token;
934
+ const cloneUrl = parsedRemote.toString();
935
+ return {
936
+ cloneUrl,
937
+ remoteUrl: gitAuth.remoteUrl,
938
+ credentials: {
939
+ username: gitAuth.username,
940
+ password: gitAuth.token
941
+ },
942
+ githubApiBaseUrl: getGitHubApiBaseUrl(gitAuth.remoteUrl)
943
+ };
944
+ } catch (error) {
945
+ if (error instanceof ApiError && error.statusCode !== 404) {
946
+ throw error;
947
+ }
948
+ const { cloneUrl } = await api.get(
949
+ `/api/mcp/repositories/${mcpId}/clone-url`
950
+ );
951
+ return parseCloneUrlAuth(cloneUrl);
952
+ }
953
+ }
954
+ function runGitWithCredentials(args, options) {
955
+ const { cwd, stdio = "ignore", credentials = null } = options ?? {};
956
+ if (!credentials) {
957
+ execFileSync("git", args, { cwd, stdio });
958
+ return;
959
+ }
960
+ const dir = mkdtempSync(join3(tmpdir(), "waniwani-askpass-"));
961
+ const askpassPath = join3(dir, "askpass.sh");
962
+ try {
963
+ writeFileSync(
964
+ askpassPath,
965
+ `#!/bin/sh
966
+ case "$1" in
967
+ *sername*) printf '%s\\n' "$WANIWANI_GIT_USERNAME" ;;
968
+ *assword*) printf '%s\\n' "$WANIWANI_GIT_PASSWORD" ;;
969
+ *) printf '\\n' ;;
970
+ esac
971
+ `,
972
+ "utf-8"
973
+ );
974
+ chmodSync(askpassPath, 448);
975
+ execFileSync("git", ["-c", "credential.helper=", ...args], {
976
+ cwd,
977
+ stdio,
978
+ env: {
979
+ ...process.env,
980
+ GIT_ASKPASS: askpassPath,
981
+ GIT_TERMINAL_PROMPT: "0",
982
+ WANIWANI_GIT_USERNAME: credentials.username,
983
+ WANIWANI_GIT_PASSWORD: credentials.password
984
+ }
985
+ });
986
+ } finally {
987
+ rmSync(dir, { recursive: true, force: true });
988
+ }
989
+ }
990
+ var REVOKE_TIMEOUT_MS = 5e3;
991
+ async function revokeGitHubInstallationToken(auth2) {
992
+ if (!auth2.credentials?.password || !auth2.githubApiBaseUrl) return;
993
+ const controller = new AbortController();
994
+ const timeout = setTimeout(() => controller.abort(), REVOKE_TIMEOUT_MS);
995
+ try {
996
+ await fetch(`${auth2.githubApiBaseUrl}/installation/token`, {
997
+ method: "DELETE",
998
+ headers: {
999
+ Accept: "application/vnd.github+json",
1000
+ Authorization: `Bearer ${auth2.credentials.password}`,
1001
+ "X-GitHub-Api-Version": "2022-11-28"
1002
+ },
1003
+ signal: controller.signal
1004
+ });
1005
+ } catch {
1006
+ } finally {
1007
+ clearTimeout(timeout);
1008
+ }
1009
+ }
1010
+
888
1011
  // src/commands/mcp/clone.ts
889
1012
  async function loadParentConfig(cwd) {
890
- const parentConfigPath = join3(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
1013
+ const parentConfigPath = join4(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
891
1014
  if (!existsSync3(parentConfigPath)) {
892
1015
  return null;
893
1016
  }
@@ -910,92 +1033,103 @@ function checkGitInstalled() {
910
1033
  );
911
1034
  }
912
1035
  }
913
- var cloneCommand = new Command5("clone").description("Clone an existing MCP project to a local directory").argument("<name>", "Name of the MCP to clone").argument("[directory]", "Directory to clone into (defaults to MCP name)").action(async (name, directory, command) => {
914
- const globalOptions = command.optsWithGlobals();
915
- const json = globalOptions.json ?? false;
916
- try {
917
- const cwd = process.cwd();
918
- const dirName = directory ?? name;
919
- const projectDir = join3(cwd, dirName);
920
- if (existsSync3(projectDir)) {
1036
+ var cloneCommand = new Command5("clone").description("Clone an existing MCP project to a local directory").argument("<name>", "Name of the MCP to clone").argument("[directory]", "Directory to clone into (defaults to MCP name)").action(
1037
+ async (name, directory, _options, command) => {
1038
+ const globalOptions = command.optsWithGlobals();
1039
+ const json = globalOptions.json ?? false;
1040
+ try {
1041
+ const cwd = process.cwd();
1042
+ const dirName = directory ?? name;
1043
+ const projectDir = join4(cwd, dirName);
1044
+ if (existsSync3(projectDir)) {
1045
+ if (json) {
1046
+ formatOutput(
1047
+ {
1048
+ success: false,
1049
+ error: `Directory "${dirName}" already exists`
1050
+ },
1051
+ true
1052
+ );
1053
+ } else {
1054
+ console.error(`Error: Directory "${dirName}" already exists`);
1055
+ }
1056
+ process.exit(1);
1057
+ }
1058
+ checkGitInstalled();
1059
+ const spinner = ora2("Fetching MCPs...").start();
1060
+ const mcps = await api.get(
1061
+ "/api/mcp/repositories"
1062
+ );
1063
+ const mcp = mcps.find((m) => m.name === name);
1064
+ if (!mcp) {
1065
+ spinner.fail("MCP not found");
1066
+ throw new McpError(
1067
+ `MCP "${name}" not found. Run 'waniwani mcp list' to see available MCPs.`
1068
+ );
1069
+ }
1070
+ spinner.text = "Cloning repository...";
1071
+ const gitAuth = await getGitAuthContext(mcp.id);
1072
+ try {
1073
+ runGitWithCredentials(["clone", gitAuth.remoteUrl, projectDir], {
1074
+ stdio: "ignore",
1075
+ credentials: gitAuth.credentials
1076
+ });
1077
+ } catch {
1078
+ spinner.fail("Failed to clone repository");
1079
+ throw new CLIError(
1080
+ "Failed to clone repository. Ensure git is configured correctly.",
1081
+ "CLONE_FAILED"
1082
+ );
1083
+ } finally {
1084
+ await revokeGitHubInstallationToken(gitAuth);
1085
+ }
1086
+ execFileSync2(
1087
+ "git",
1088
+ ["remote", "set-url", "origin", mcp.githubCloneUrl],
1089
+ {
1090
+ cwd: projectDir,
1091
+ stdio: "ignore"
1092
+ }
1093
+ );
1094
+ const parentConfig = await loadParentConfig(cwd);
1095
+ await initConfigAt(projectDir, {
1096
+ ...parentConfig,
1097
+ mcpId: mcp.id
1098
+ });
1099
+ spinner.succeed("Repository cloned");
921
1100
  if (json) {
922
1101
  formatOutput(
923
1102
  {
924
- success: false,
925
- error: `Directory "${dirName}" already exists`
1103
+ success: true,
1104
+ projectDir,
1105
+ mcpId: mcp.id
926
1106
  },
927
1107
  true
928
1108
  );
929
1109
  } else {
930
- console.error(`Error: Directory "${dirName}" already exists`);
1110
+ console.log();
1111
+ formatSuccess(`MCP "${name}" cloned!`, false);
1112
+ console.log();
1113
+ console.log("Next steps:");
1114
+ console.log(` cd ${dirName}`);
1115
+ console.log(" waniwani mcp preview # Start developing");
931
1116
  }
1117
+ } catch (error) {
1118
+ handleError(error, json);
932
1119
  process.exit(1);
933
1120
  }
934
- checkGitInstalled();
935
- const spinner = ora2("Fetching MCPs...").start();
936
- const mcps = await api.get(
937
- "/api/mcp/repositories"
938
- );
939
- const mcp = mcps.find((m) => m.name === name);
940
- if (!mcp) {
941
- spinner.fail("MCP not found");
942
- throw new McpError(
943
- `MCP "${name}" not found. Run 'waniwani mcp list' to see available MCPs.`
944
- );
945
- }
946
- spinner.text = "Cloning repository...";
947
- const { cloneUrl } = await api.get(
948
- `/api/mcp/repositories/${mcp.id}/clone-url`
949
- );
950
- try {
951
- execSync(`git clone "${cloneUrl}" "${projectDir}"`, {
952
- stdio: "ignore"
953
- });
954
- } catch {
955
- spinner.fail("Failed to clone repository");
956
- throw new CLIError(
957
- "Failed to clone repository. Ensure git is configured correctly.",
958
- "CLONE_FAILED"
959
- );
960
- }
961
- const parentConfig = await loadParentConfig(cwd);
962
- await initConfigAt(projectDir, {
963
- ...parentConfig,
964
- mcpId: mcp.id
965
- });
966
- spinner.succeed("Repository cloned");
967
- if (json) {
968
- formatOutput(
969
- {
970
- success: true,
971
- projectDir,
972
- mcpId: mcp.id
973
- },
974
- true
975
- );
976
- } else {
977
- console.log();
978
- formatSuccess(`MCP "${name}" cloned!`, false);
979
- console.log();
980
- console.log("Next steps:");
981
- console.log(` cd ${dirName}`);
982
- console.log(" waniwani mcp preview # Start developing");
983
- }
984
- } catch (error) {
985
- handleError(error, json);
986
- process.exit(1);
987
1121
  }
988
- });
1122
+ );
989
1123
 
990
1124
  // src/commands/mcp/create.ts
991
- import { execSync as execSync2 } from "child_process";
1125
+ import { execFileSync as execFileSync3, execSync as execSync2 } from "child_process";
992
1126
  import { existsSync as existsSync4 } from "fs";
993
1127
  import { readFile as readFile3 } from "fs/promises";
994
- import { join as join4 } from "path";
1128
+ import { join as join5 } from "path";
995
1129
  import { Command as Command6 } from "commander";
996
1130
  import ora3 from "ora";
997
1131
  async function loadParentConfig2(cwd) {
998
- const parentConfigPath = join4(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
1132
+ const parentConfigPath = join5(cwd, LOCAL_CONFIG_DIR, CONFIG_FILE_NAME);
999
1133
  if (!existsSync4(parentConfigPath)) {
1000
1134
  return null;
1001
1135
  }
@@ -1023,7 +1157,7 @@ var createCommand = new Command6("create").description("Create a new MCP project
1023
1157
  const json = globalOptions.json ?? false;
1024
1158
  try {
1025
1159
  const cwd = process.cwd();
1026
- const projectDir = join4(cwd, name);
1160
+ const projectDir = join5(cwd, name);
1027
1161
  if (existsSync4(projectDir)) {
1028
1162
  if (json) {
1029
1163
  formatOutput(
@@ -1044,12 +1178,11 @@ var createCommand = new Command6("create").description("Create a new MCP project
1044
1178
  name
1045
1179
  });
1046
1180
  spinner.text = "Cloning repository...";
1047
- const { cloneUrl } = await api.get(
1048
- `/api/mcp/repositories/${result.id}/clone-url`
1049
- );
1181
+ const gitAuth = await getGitAuthContext(result.id);
1050
1182
  try {
1051
- execSync2(`git clone "${cloneUrl}" "${projectDir}"`, {
1052
- stdio: "ignore"
1183
+ runGitWithCredentials(["clone", gitAuth.remoteUrl, projectDir], {
1184
+ stdio: "ignore",
1185
+ credentials: gitAuth.credentials
1053
1186
  });
1054
1187
  } catch {
1055
1188
  spinner.fail("Failed to clone repository");
@@ -1057,7 +1190,17 @@ var createCommand = new Command6("create").description("Create a new MCP project
1057
1190
  `Failed to clone repository. Ensure git is configured correctly.`,
1058
1191
  "CLONE_FAILED"
1059
1192
  );
1193
+ } finally {
1194
+ await revokeGitHubInstallationToken(gitAuth);
1060
1195
  }
1196
+ execFileSync3(
1197
+ "git",
1198
+ ["remote", "set-url", "origin", result.githubCloneUrl],
1199
+ {
1200
+ cwd: projectDir,
1201
+ stdio: "ignore"
1202
+ }
1203
+ );
1061
1204
  const parentConfig = await loadParentConfig2(cwd);
1062
1205
  await initConfigAt(projectDir, {
1063
1206
  ...parentConfig,
@@ -1340,7 +1483,7 @@ var fileCommand = new Command11("file").description("File operations in MCP sand
1340
1483
  import chalk6 from "chalk";
1341
1484
  import { Command as Command12 } from "commander";
1342
1485
  import ora8 from "ora";
1343
- var listCommand2 = new Command12("list").description("List all MCPs in your organization").action(async (_, command) => {
1486
+ var listCommand2 = new Command12("list").description("List all MCPs in your organization").action(async (_options, command) => {
1344
1487
  const globalOptions = command.optsWithGlobals();
1345
1488
  const json = globalOptions.json ?? false;
1346
1489
  try {
@@ -1544,24 +1687,44 @@ import { watch } from "chokidar";
1544
1687
  import { Command as Command14 } from "commander";
1545
1688
  import ora10 from "ora";
1546
1689
 
1690
+ // src/lib/async.ts
1691
+ async function withTimeout(promise, timeoutMs, options) {
1692
+ let timer;
1693
+ try {
1694
+ return await Promise.race([
1695
+ promise,
1696
+ new Promise((resolve) => {
1697
+ timer = setTimeout(() => {
1698
+ options?.onTimeout?.();
1699
+ resolve(null);
1700
+ }, timeoutMs);
1701
+ })
1702
+ ]);
1703
+ } finally {
1704
+ if (timer) {
1705
+ clearTimeout(timer);
1706
+ }
1707
+ }
1708
+ }
1709
+
1547
1710
  // src/lib/sync.ts
1548
1711
  import { existsSync as existsSync5 } from "fs";
1549
1712
  import { mkdir as mkdir2, readdir, readFile as readFile5, stat, writeFile as writeFile3 } from "fs/promises";
1550
- import { dirname, join as join5, relative } from "path";
1713
+ import { dirname, join as join6, relative } from "path";
1551
1714
  import ignore from "ignore";
1552
1715
  var PROJECT_DIR = ".waniwani";
1553
1716
  async function findProjectRoot(startDir) {
1554
1717
  let current = startDir;
1555
1718
  const root = dirname(current);
1556
1719
  while (current !== root) {
1557
- if (existsSync5(join5(current, PROJECT_DIR))) {
1720
+ if (existsSync5(join6(current, PROJECT_DIR))) {
1558
1721
  return current;
1559
1722
  }
1560
1723
  const parent = dirname(current);
1561
1724
  if (parent === current) break;
1562
1725
  current = parent;
1563
1726
  }
1564
- if (existsSync5(join5(current, PROJECT_DIR))) {
1727
+ if (existsSync5(join6(current, PROJECT_DIR))) {
1565
1728
  return current;
1566
1729
  }
1567
1730
  return null;
@@ -1585,7 +1748,7 @@ var DEFAULT_IGNORE_PATTERNS = [
1585
1748
  async function loadIgnorePatterns(projectRoot) {
1586
1749
  const ig = ignore();
1587
1750
  ig.add(DEFAULT_IGNORE_PATTERNS);
1588
- const gitignorePath = join5(projectRoot, ".gitignore");
1751
+ const gitignorePath = join6(projectRoot, ".gitignore");
1589
1752
  if (existsSync5(gitignorePath)) {
1590
1753
  try {
1591
1754
  const content = await readFile5(gitignorePath, "utf-8");
@@ -1601,7 +1764,7 @@ async function collectFiles(projectRoot) {
1601
1764
  async function walk(dir) {
1602
1765
  const entries = await readdir(dir, { withFileTypes: true });
1603
1766
  for (const entry of entries) {
1604
- const fullPath = join5(dir, entry.name);
1767
+ const fullPath = join6(dir, entry.name);
1605
1768
  const relativePath = relative(projectRoot, fullPath);
1606
1769
  if (ig.ignores(relativePath)) {
1607
1770
  continue;
@@ -1631,7 +1794,7 @@ async function pullFilesFromGithub(mcpId, targetDir) {
1631
1794
  );
1632
1795
  const writtenFiles = [];
1633
1796
  for (const file of result.files) {
1634
- const localPath = join5(targetDir, file.path);
1797
+ const localPath = join6(targetDir, file.path);
1635
1798
  const dir = dirname(localPath);
1636
1799
  await mkdir2(dir, { recursive: true });
1637
1800
  if (file.encoding === "base64") {
@@ -1644,7 +1807,7 @@ async function pullFilesFromGithub(mcpId, targetDir) {
1644
1807
  return { count: writtenFiles.length, files: writtenFiles };
1645
1808
  }
1646
1809
  async function collectSingleFile(projectRoot, filePath) {
1647
- const fullPath = join5(projectRoot, filePath);
1810
+ const fullPath = join6(projectRoot, filePath);
1648
1811
  const relativePath = relative(projectRoot, fullPath);
1649
1812
  if (!existsSync5(fullPath)) {
1650
1813
  return null;
@@ -1667,6 +1830,8 @@ async function collectSingleFile(projectRoot, filePath) {
1667
1830
  }
1668
1831
 
1669
1832
  // src/commands/mcp/preview.ts
1833
+ var SHUTDOWN_MAX_WAIT_MS = 3e3;
1834
+ var SHUTDOWN_STEP_TIMEOUT_MS = 1200;
1670
1835
  var previewCommand = new Command14("preview").description("Start live development with sandbox and file watching").option("--mcp-id <id>", "Specific MCP ID").option("--no-watch", "Skip file watching").option("--no-logs", "Don't stream logs to terminal").action(async (options, command) => {
1671
1836
  const globalOptions = command.optsWithGlobals();
1672
1837
  const json = globalOptions.json ?? false;
@@ -1767,11 +1932,51 @@ var previewCommand = new Command14("preview").description("Start live developmen
1767
1932
  const relativePath = filePath.replace(`${projectRoot}/`, "");
1768
1933
  console.log(` Deleted: ${relativePath}`);
1769
1934
  });
1770
- process.on("SIGINT", async () => {
1935
+ const stopSessionBestEffort = async () => {
1936
+ await withTimeout(
1937
+ api.post(`/api/mcp/sessions/${sessionId}/server`, { action: "stop" }).catch(() => void 0),
1938
+ SHUTDOWN_STEP_TIMEOUT_MS
1939
+ );
1940
+ await withTimeout(
1941
+ api.delete(`/api/mcp/sessions/${sessionId}`).catch(() => void 0),
1942
+ SHUTDOWN_STEP_TIMEOUT_MS
1943
+ );
1944
+ await withTimeout(
1945
+ config.setSessionId(null).catch(() => void 0),
1946
+ SHUTDOWN_STEP_TIMEOUT_MS
1947
+ );
1948
+ };
1949
+ let shuttingDown = false;
1950
+ const gracefulShutdown = async () => {
1951
+ if (shuttingDown) return;
1952
+ shuttingDown = true;
1771
1953
  console.log();
1772
1954
  console.log("Stopping development environment...");
1773
- await watcher.close();
1774
- process.exit(0);
1955
+ try {
1956
+ await withTimeout(
1957
+ (async () => {
1958
+ await withTimeout(
1959
+ watcher.close().catch(() => void 0),
1960
+ SHUTDOWN_STEP_TIMEOUT_MS
1961
+ );
1962
+ await stopSessionBestEffort();
1963
+ })(),
1964
+ SHUTDOWN_MAX_WAIT_MS,
1965
+ {
1966
+ onTimeout: () => {
1967
+ console.log("Shutdown timed out, forcing exit.");
1968
+ }
1969
+ }
1970
+ );
1971
+ } finally {
1972
+ process.exit(0);
1973
+ }
1974
+ };
1975
+ process.once("SIGINT", () => {
1976
+ void gracefulShutdown();
1977
+ });
1978
+ process.once("SIGTERM", () => {
1979
+ void gracefulShutdown();
1775
1980
  });
1776
1981
  await new Promise(() => {
1777
1982
  });
@@ -1783,6 +1988,7 @@ var previewCommand = new Command14("preview").description("Start live developmen
1783
1988
  });
1784
1989
 
1785
1990
  // src/commands/mcp/publish.ts
1991
+ import { execFileSync as execFileSync4 } from "child_process";
1786
1992
  import { input } from "@inquirer/prompts";
1787
1993
  import { Command as Command15 } from "commander";
1788
1994
  import ora11 from "ora";
@@ -1798,6 +2004,29 @@ var publishCommand = new Command15("publish").description("Push local files to G
1798
2004
  "NOT_IN_PROJECT"
1799
2005
  );
1800
2006
  }
2007
+ try {
2008
+ execFileSync4("git", ["rev-parse", "--is-inside-work-tree"], {
2009
+ cwd: projectRoot,
2010
+ stdio: "ignore"
2011
+ });
2012
+ } catch {
2013
+ throw new CLIError(
2014
+ "Not a git repository. Run 'waniwani mcp create <name>' or 'waniwani mcp clone <name>' to set up properly.",
2015
+ "NOT_GIT_REPO"
2016
+ );
2017
+ }
2018
+ const status = execFileSync4("git", ["status", "--porcelain"], {
2019
+ cwd: projectRoot,
2020
+ encoding: "utf-8"
2021
+ }).trim();
2022
+ if (!status) {
2023
+ if (json) {
2024
+ formatOutput({ success: true, message: "Nothing to publish" }, true);
2025
+ } else {
2026
+ console.log("Nothing to publish \u2014 no changes detected.");
2027
+ }
2028
+ return;
2029
+ }
1801
2030
  let message = options.message;
1802
2031
  if (!message) {
1803
2032
  message = await input({
@@ -1805,20 +2034,31 @@ var publishCommand = new Command15("publish").description("Push local files to G
1805
2034
  validate: (value) => value.trim() ? true : "Commit message is required"
1806
2035
  });
1807
2036
  }
1808
- const spinner = ora11("Collecting files...").start();
1809
- const files = await collectFiles(projectRoot);
1810
- if (files.length === 0) {
1811
- spinner.fail("No files to deploy");
1812
- return;
1813
- }
1814
- spinner.text = `Pushing ${files.length} files to GitHub...`;
1815
- const result = await api.post(
1816
- `/api/mcp/repositories/${mcpId}/deploy`,
1817
- { files, message }
1818
- );
1819
- spinner.succeed(`Pushed to GitHub (${result.commitSha.slice(0, 7)})`);
2037
+ const spinner = ora11("Publishing...").start();
2038
+ const gitAuth = await getGitAuthContext(mcpId);
2039
+ spinner.text = "Committing changes...";
2040
+ execFileSync4("git", ["add", "-A"], { cwd: projectRoot, stdio: "ignore" });
2041
+ execFileSync4("git", ["commit", "-m", message], {
2042
+ cwd: projectRoot,
2043
+ stdio: "ignore"
2044
+ });
2045
+ spinner.text = "Pushing to GitHub...";
2046
+ try {
2047
+ runGitWithCredentials(["push", gitAuth.remoteUrl, "HEAD"], {
2048
+ cwd: projectRoot,
2049
+ stdio: "ignore",
2050
+ credentials: gitAuth.credentials
2051
+ });
2052
+ } finally {
2053
+ await revokeGitHubInstallationToken(gitAuth);
2054
+ }
2055
+ const commitSha = execFileSync4("git", ["rev-parse", "HEAD"], {
2056
+ cwd: projectRoot,
2057
+ encoding: "utf-8"
2058
+ }).trim();
2059
+ spinner.succeed(`Pushed to GitHub (${commitSha.slice(0, 7)})`);
1820
2060
  if (json) {
1821
- formatOutput(result, true);
2061
+ formatOutput({ commitSha, message }, true);
1822
2062
  } else {
1823
2063
  console.log();
1824
2064
  formatSuccess("Files pushed to GitHub!", false);
@@ -2082,7 +2322,7 @@ import { Command as Command24 } from "commander";
2082
2322
  import chalk10 from "chalk";
2083
2323
  import { Command as Command22 } from "commander";
2084
2324
  import ora17 from "ora";
2085
- var listCommand3 = new Command22("list").description("List your organizations").action(async (_, command) => {
2325
+ var listCommand3 = new Command22("list").description("List your organizations").action(async (_options, command) => {
2086
2326
  const globalOptions = command.optsWithGlobals();
2087
2327
  const json = globalOptions.json ?? false;
2088
2328
  try {
@@ -2134,7 +2374,7 @@ var listCommand3 = new Command22("list").description("List your organizations").
2134
2374
  // src/commands/org/switch.ts
2135
2375
  import { Command as Command23 } from "commander";
2136
2376
  import ora18 from "ora";
2137
- var switchCommand = new Command23("switch").description("Switch to a different organization").argument("<name>", "Name or slug of the organization to switch to").action(async (name, _, command) => {
2377
+ var switchCommand = new Command23("switch").description("Switch to a different organization").argument("<name>", "Name or slug of the organization to switch to").action(async (name, _options, command) => {
2138
2378
  const globalOptions = command.optsWithGlobals();
2139
2379
  const json = globalOptions.json ?? false;
2140
2380
  try {