centaurus-cli 3.1.1 → 3.1.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.
@@ -1,6 +1,5 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
- import { WelcomeBanner } from "./WelcomeBanner.js";
4
3
  import { SelectPrompt } from "./SelectPrompt.js";
5
4
  const AuthWelcomeScreen = ({
6
5
  onSignIn,
@@ -13,7 +12,7 @@ const AuthWelcomeScreen = ({
13
12
  onExit();
14
13
  }
15
14
  };
16
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(WelcomeBanner, null), /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, paddingX: 1 }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "#00ccff" }, "\u{1F510} Authentication Required")), /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, null, "To use Centaurus CLI, you need to sign in with Google."))), /* @__PURE__ */ React.createElement(Box, { paddingX: 1 }, /* @__PURE__ */ React.createElement(
15
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, paddingX: 1 }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { bold: true, color: "#00ccff" }, "\u{1F510} Authentication Required")), /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, null, "To use Centaurus CLI, you need to sign in with Google."))), /* @__PURE__ */ React.createElement(Box, { paddingX: 1 }, /* @__PURE__ */ React.createElement(
17
16
  SelectPrompt,
18
17
  {
19
18
  message: "What would you like to do?",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/components/AuthWelcomeScreen.tsx"],"sourcesContent":["import React, { useState } from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport { WelcomeBanner } from './WelcomeBanner.js';\r\nimport { SelectPrompt } from './SelectPrompt.js';\r\n\r\ninterface AuthWelcomeScreenProps {\r\n onSignIn: () => void;\r\n onExit: () => void;\r\n}\r\n\r\nexport const AuthWelcomeScreen: React.FC<AuthWelcomeScreenProps> = ({\r\n onSignIn,\r\n onExit,\r\n}) => {\r\n const handleSelect = (value: string) => {\r\n if (value === 'signin') {\r\n onSignIn();\r\n } else {\r\n onExit();\r\n }\r\n };\r\n\r\n return (\r\n <Box flexDirection=\"column\">\r\n {/* Welcome Banner — rendered flush to terminal edges; it manages its own width */}\r\n <WelcomeBanner />\r\n\r\n {/* Authentication Info */}\r\n <Box flexDirection=\"column\" marginTop={1} marginBottom={1} paddingX={1}>\r\n <Box marginBottom={1}>\r\n <Text bold color=\"#00ccff\">🔐 Authentication Required</Text>\r\n </Box>\r\n\r\n <Box marginBottom={1}>\r\n <Text>\r\n To use Centaurus CLI, you need to sign in with Google.\r\n </Text>\r\n </Box>\r\n\r\n\r\n </Box>\r\n\r\n {/* Picker */}\r\n <Box paddingX={1}>\r\n <SelectPrompt\r\n message=\"What would you like to do?\"\r\n choices={[\r\n { label: '🔑 Sign in with Google', value: 'signin' },\r\n { label: '❌ Exit', value: 'exit' },\r\n ]}\r\n onSelect={handleSelect}\r\n width={(process.stdout.columns || 120) - 2}\r\n />\r\n </Box>\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"AAAA,OAAO,WAAyB;AAChC,SAAS,KAAK,YAAY;AAC1B,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAOtB,MAAM,oBAAsD,CAAC;AAAA,EAClE;AAAA,EACA;AACF,MAAM;AACJ,QAAM,eAAe,CAAC,UAAkB;AACtC,QAAI,UAAU,UAAU;AACtB,eAAS;AAAA,IACX,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SACE,oCAAC,OAAI,eAAc,YAEjB,oCAAC,mBAAc,GAGf,oCAAC,OAAI,eAAc,UAAS,WAAW,GAAG,cAAc,GAAG,UAAU,KACnE,oCAAC,OAAI,cAAc,KACjB,oCAAC,QAAK,MAAI,MAAC,OAAM,aAAU,mCAA0B,CACvD,GAEA,oCAAC,OAAI,cAAc,KACjB,oCAAC,YAAK,wDAEN,CACF,CAGF,GAGA,oCAAC,OAAI,UAAU,KACb;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,SAAS;AAAA,QACP,EAAE,OAAO,iCAA0B,OAAO,SAAS;AAAA,QACnD,EAAE,OAAO,eAAU,OAAO,OAAO;AAAA,MACnC;AAAA,MACA,UAAU;AAAA,MACV,QAAQ,QAAQ,OAAO,WAAW,OAAO;AAAA;AAAA,EAC3C,CACF,CACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/ui/components/AuthWelcomeScreen.tsx"],"sourcesContent":["import React from 'react';\r\nimport { Box, Text } from 'ink';\r\nimport { SelectPrompt } from './SelectPrompt.js';\r\n\r\ninterface AuthWelcomeScreenProps {\r\n onSignIn: () => void;\r\n onExit: () => void;\r\n}\r\n\r\nexport const AuthWelcomeScreen: React.FC<AuthWelcomeScreenProps> = ({\r\n onSignIn,\r\n onExit,\r\n}) => {\r\n const handleSelect = (value: string) => {\r\n if (value === 'signin') {\r\n onSignIn();\r\n } else {\r\n onExit();\r\n }\r\n };\r\n\r\n return (\r\n <Box flexDirection=\"column\">\r\n {/* Authentication Info */}\r\n <Box flexDirection=\"column\" marginTop={1} marginBottom={1} paddingX={1}>\r\n <Box marginBottom={1}>\r\n <Text bold color=\"#00ccff\">🔐 Authentication Required</Text>\r\n </Box>\r\n\r\n <Box marginBottom={1}>\r\n <Text>\r\n To use Centaurus CLI, you need to sign in with Google.\r\n </Text>\r\n </Box>\r\n </Box>\r\n\r\n {/* Picker */}\r\n <Box paddingX={1}>\r\n <SelectPrompt\r\n message=\"What would you like to do?\"\r\n choices={[\r\n { label: '🔑 Sign in with Google', value: 'signin' },\r\n { label: '❌ Exit', value: 'exit' },\r\n ]}\r\n onSelect={handleSelect}\r\n width={(process.stdout.columns || 120) - 2}\r\n />\r\n </Box>\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"AAAA,OAAO,WAAW;AAClB,SAAS,KAAK,YAAY;AAC1B,SAAS,oBAAoB;AAOtB,MAAM,oBAAsD,CAAC;AAAA,EAClE;AAAA,EACA;AACF,MAAM;AACJ,QAAM,eAAe,CAAC,UAAkB;AACtC,QAAI,UAAU,UAAU;AACtB,eAAS;AAAA,IACX,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SACE,oCAAC,OAAI,eAAc,YAEjB,oCAAC,OAAI,eAAc,UAAS,WAAW,GAAG,cAAc,GAAG,UAAU,KACnE,oCAAC,OAAI,cAAc,KACjB,oCAAC,QAAK,MAAI,MAAC,OAAM,aAAU,mCAA0B,CACvD,GAEA,oCAAC,OAAI,cAAc,KACjB,oCAAC,YAAK,wDAEN,CACF,CACF,GAGA,oCAAC,OAAI,UAAU,KACb;AAAA,IAAC;AAAA;AAAA,MACC,SAAQ;AAAA,MACR,SAAS;AAAA,QACP,EAAE,OAAO,iCAA0B,OAAO,SAAS;AAAA,QACnD,EAAE,OAAO,eAAU,OAAO,OAAO;AAAA,MACnC;AAAA,MACA,UAAU;AAAA,MACV,QAAQ,QAAQ,OAAO,WAAW,OAAO;AAAA;AAAA,EAC3C,CACF,CACF;AAEJ;","names":[]}
@@ -3,6 +3,32 @@ import { Box, Text, useApp } from "ink";
3
3
  import Spinner from "ink-spinner";
4
4
  import { spawn } from "child_process";
5
5
  import { logInfo } from "../../utils/logger.js";
6
+ function writeStaticUpdateHeader(currentVersion, latestVersion) {
7
+ const cols = process.stdout.columns || 80;
8
+ const innerW = Math.max(cols - 4, 40);
9
+ const cyan = "\x1B[36m";
10
+ const green = "\x1B[32m";
11
+ const dim = "\x1B[2m";
12
+ const bold = "\x1B[1m";
13
+ const reset = "\x1B[0m";
14
+ const top = `${cyan}\u256D${"\u2500".repeat(innerW)}\u256E${reset}`;
15
+ const bottom = `${cyan}\u2502${" ".repeat(innerW)}\u2502${reset}`;
16
+ const border = (line) => {
17
+ const plainLen = line.replace(/\x1b\[[0-9;]*m/g, "").length;
18
+ const pad = Math.max(0, innerW - plainLen);
19
+ return `${cyan}\u2502${reset} ${line}${" ".repeat(pad > 0 ? pad - 1 : 0)}${cyan}\u2502${reset}`;
20
+ };
21
+ const lines = [
22
+ top,
23
+ border(`${cyan}${bold}\u{1F680} New Update Available!${reset}`),
24
+ border(""),
25
+ border(`Current: ${dim}${currentVersion}${reset} \u2192 Latest: ${green}${bold}${latestVersion}${reset}`),
26
+ border(""),
27
+ border(`${dim}Please wait while we update Centaurus to the latest version.${reset}`),
28
+ border("")
29
+ ];
30
+ process.stdout.write(lines.join("\n") + "\n");
31
+ }
6
32
  const VersionUpdatePrompt = ({
7
33
  currentVersion,
8
34
  latestVersion,
@@ -11,9 +37,12 @@ const VersionUpdatePrompt = ({
11
37
  const [status, setStatus] = useState("pending");
12
38
  const [errorMessage, setErrorMessage] = useState("");
13
39
  const hasStartedRef = useRef(false);
40
+ const headerWrittenRef = useRef(false);
14
41
  const { exit } = useApp();
15
42
  useEffect(() => {
16
- process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
43
+ if (headerWrittenRef.current) return;
44
+ headerWrittenRef.current = true;
45
+ writeStaticUpdateHeader(currentVersion, latestVersion);
17
46
  }, []);
18
47
  useEffect(() => {
19
48
  if (hasStartedRef.current) return;
@@ -83,7 +112,7 @@ const VersionUpdatePrompt = ({
83
112
  logInfo('User should run "centaurus" to start the updated CLI.');
84
113
  process.exit(0);
85
114
  };
86
- return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 2, paddingY: 1, marginTop: 1 }, /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "cyan", bold: true }, "\u{1F680} New Update Available!")), /* @__PURE__ */ React.createElement(Box, { marginBottom: 1 }, /* @__PURE__ */ React.createElement(Text, null, "Current: ", /* @__PURE__ */ React.createElement(Text, { color: "gray" }, currentVersion), " \u2192 Latest: ", /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, latestVersion))), status === "pending" && /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" }), " Preparing to update...")), status === "installing" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" }), " Installing update...")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Please wait while we update Centaurus to the latest version."))), status === "success" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, "\u2705 Update successful!")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Restarting Centaurus..."))), status === "error" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Box, null, /* @__PURE__ */ React.createElement(Text, { color: "red", bold: true }, "\u274C Update Failed")), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, errorMessage)), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Continuing to chat in a few seconds..."))));
115
+ return /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, status === "pending" && /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" }), " Preparing to update..."), status === "installing" && /* @__PURE__ */ React.createElement(Text, { color: "cyan" }, /* @__PURE__ */ React.createElement(Spinner, { type: "dots" }), " Installing update..."), status === "success" && /* @__PURE__ */ React.createElement(Text, { color: "green", bold: true }, "\u2705 Update successful! Restarting Centaurus..."), status === "error" && /* @__PURE__ */ React.createElement(Box, { flexDirection: "column" }, /* @__PURE__ */ React.createElement(Text, { color: "red", bold: true }, "\u274C Update Failed"), /* @__PURE__ */ React.createElement(Text, { color: "yellow" }, errorMessage), /* @__PURE__ */ React.createElement(Text, { dimColor: true }, "Continuing to chat in a few seconds...")));
87
116
  };
88
117
  export {
89
118
  VersionUpdatePrompt
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/ui/components/VersionUpdatePrompt.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef } from 'react';\r\nimport { Box, Text, useApp } from 'ink';\r\nimport Spinner from 'ink-spinner';\r\nimport { spawn } from 'child_process';\r\nimport { logInfo } from '../../utils/logger.js';\r\n\r\ninterface VersionUpdatePromptProps {\r\n currentVersion: string;\r\n latestVersion: string;\r\n onComplete: () => void;\r\n}\r\n\r\ntype UpdateStatus = 'pending' | 'installing' | 'success' | 'error';\r\n\r\nexport const VersionUpdatePrompt: React.FC<VersionUpdatePromptProps> = ({\r\n currentVersion,\r\n latestVersion,\r\n onComplete\r\n}) => {\r\n const [status, setStatus] = useState<UpdateStatus>('pending');\r\n const [errorMessage, setErrorMessage] = useState<string>('');\r\n const hasStartedRef = useRef(false);\r\n const { exit } = useApp();\r\n\r\n // Clear the screen on mount to prevent banner duplication from Static component\r\n useEffect(() => {\r\n // Clear terminal to remove any previous Static content that might persist\r\n process.stdout.write('\\x1b[2J\\x1b[3J\\x1b[H');\r\n }, []);\r\n\r\n // Auto-start installation after a brief delay\r\n useEffect(() => {\r\n if (hasStartedRef.current) return;\r\n hasStartedRef.current = true;\r\n\r\n const timer = setTimeout(() => {\r\n setStatus('installing');\r\n performUpdate();\r\n }, 1500); // Brief delay so user can see the update notification\r\n\r\n return () => clearTimeout(timer);\r\n }, []);\r\n\r\n const performUpdate = () => {\r\n // Determine the package manager - prefer npm as it's most commonly used\r\n const isWindows = process.platform === 'win32';\r\n const npmCmd = isWindows ? 'npm.cmd' : 'npm';\r\n\r\n const installProcess = spawn(\r\n npmCmd,\r\n ['install', '-g', 'centaurus-cli@latest'],\r\n {\r\n shell: true,\r\n stdio: 'pipe', // Capture output for error handling\r\n detached: false\r\n }\r\n );\r\n\r\n let stderrOutput = '';\r\n let stdoutOutput = '';\r\n\r\n if (installProcess.stdout) {\r\n installProcess.stdout.on('data', (data) => {\r\n stdoutOutput += data.toString();\r\n });\r\n }\r\n\r\n if (installProcess.stderr) {\r\n installProcess.stderr.on('data', (data) => {\r\n stderrOutput += data.toString();\r\n });\r\n }\r\n\r\n installProcess.on('error', (err) => {\r\n setStatus('error');\r\n setErrorMessage(`Failed to start update: ${err.message}`);\r\n });\r\n\r\n installProcess.on('close', (code) => {\r\n if (code === 0) {\r\n setStatus('success');\r\n // Wait a moment so user can see success message, then restart\r\n setTimeout(() => {\r\n restartCLI();\r\n }, 1500);\r\n } else {\r\n setStatus('error');\r\n // Parse common error messages for better user feedback\r\n let friendlyError = 'Update failed. Please try manually: npm install -g centaurus-cli@latest';\r\n\r\n if (stderrOutput.includes('EACCES') || stderrOutput.includes('permission')) {\r\n friendlyError = 'Permission denied. Try running with sudo (Linux/Mac) or as Administrator (Windows).';\r\n } else if (stderrOutput.includes('ENOTFOUND') || stderrOutput.includes('network')) {\r\n friendlyError = 'Network error. Please check your internet connection and try again.';\r\n } else if (stderrOutput.includes('ETIMEOUT')) {\r\n friendlyError = 'Connection timed out. Please check your internet connection and try again.';\r\n }\r\n\r\n setErrorMessage(friendlyError);\r\n\r\n // After showing error, continue to chat after a delay\r\n setTimeout(() => {\r\n onComplete();\r\n }, 5000);\r\n }\r\n });\r\n };\r\n\r\n const restartCLI = () => {\r\n // Exit the Ink app first\r\n exit();\r\n\r\n // Clear screen and show success message\r\n process.stdout.write('\\x1b[2J\\x1b[3J\\x1b[H');\r\n logInfo('Update complete! Centaurus has been updated to the latest version.');\r\n logInfo('User should run \"centaurus\" to start the updated CLI.');\r\n\r\n process.exit(0);\r\n };\r\n\r\n return (\r\n <Box flexDirection=\"column\" borderStyle=\"round\" borderColor=\"cyan\" paddingX={2} paddingY={1} marginTop={1}>\r\n <Box marginBottom={1}>\r\n <Text color=\"cyan\" bold>🚀 New Update Available!</Text>\r\n </Box>\r\n\r\n <Box marginBottom={1}>\r\n <Text>\r\n Current: <Text color=\"gray\">{currentVersion}</Text> Latest: <Text color=\"green\" bold>{latestVersion}</Text>\r\n </Text>\r\n </Box>\r\n\r\n {status === 'pending' && (\r\n <Box>\r\n <Text color=\"yellow\">\r\n <Spinner type=\"dots\" /> Preparing to update...\r\n </Text>\r\n </Box>\r\n )}\r\n\r\n {status === 'installing' && (\r\n <Box flexDirection=\"column\">\r\n <Box>\r\n <Text color=\"cyan\">\r\n <Spinner type=\"dots\" /> Installing update...\r\n </Text>\r\n </Box>\r\n <Box marginTop={1}>\r\n <Text dimColor>Please wait while we update Centaurus to the latest version.</Text>\r\n </Box>\r\n </Box>\r\n )}\r\n\r\n {status === 'success' && (\r\n <Box flexDirection=\"column\">\r\n <Box>\r\n <Text color=\"green\" bold>✅ Update successful!</Text>\r\n </Box>\r\n <Box marginTop={1}>\r\n <Text dimColor>Restarting Centaurus...</Text>\r\n </Box>\r\n </Box>\r\n )}\r\n\r\n {status === 'error' && (\r\n <Box flexDirection=\"column\">\r\n <Box>\r\n <Text color=\"red\" bold>❌ Update Failed</Text>\r\n </Box>\r\n <Box marginTop={1}>\r\n <Text color=\"yellow\">{errorMessage}</Text>\r\n </Box>\r\n <Box marginTop={1}>\r\n <Text dimColor>Continuing to chat in a few seconds...</Text>\r\n </Box>\r\n </Box>\r\n )}\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/ui/components/VersionUpdatePrompt.tsx"],"sourcesContent":["import React, { useState, useEffect, useRef } from 'react';\r\nimport { Box, Text, useApp } from 'ink';\r\nimport Spinner from 'ink-spinner';\r\nimport { spawn } from 'child_process';\r\nimport { logInfo } from '../../utils/logger.js';\r\n\r\ninterface VersionUpdatePromptProps {\r\n currentVersion: string;\r\n latestVersion: string;\r\n onComplete: () => void;\r\n}\r\n\r\ntype UpdateStatus = 'pending' | 'installing' | 'success' | 'error';\r\n\r\n/**\r\n * Static header block written once to stdout.\r\n * This content never re-renders, avoiding the banner/duplicate flicker\r\n * that happens when Ink redraws on every spinner tick.\r\n */\r\nfunction writeStaticUpdateHeader(currentVersion: string, latestVersion: string): void {\r\n const cols = process.stdout.columns || 80;\r\n const innerW = Math.max(cols - 4, 40);\r\n const cyan = '\\x1b[36m';\r\n const green = '\\x1b[32m';\r\n const dim = '\\x1b[2m';\r\n const bold = '\\x1b[1m';\r\n const reset = '\\x1b[0m';\r\n\r\n const top = `${cyan}╭${'─'.repeat(innerW)}╮${reset}`;\r\n const bottom = `${cyan}│${' '.repeat(innerW)}│${reset}`;\r\n const border = (line: string) => {\r\n // Pad line to fill the box width (accounting for ANSI codes)\r\n const plainLen = line.replace(/\\x1b\\[[0-9;]*m/g, '').length;\r\n const pad = Math.max(0, innerW - plainLen);\r\n return `${cyan}│${reset} ${line}${' '.repeat(pad > 0 ? pad - 1 : 0)}${cyan}│${reset}`;\r\n };\r\n\r\n const lines = [\r\n top,\r\n border(`${cyan}${bold}🚀 New Update Available!${reset}`),\r\n border(''),\r\n border(`Current: ${dim}${currentVersion}${reset} → Latest: ${green}${bold}${latestVersion}${reset}`),\r\n border(''),\r\n border(`${dim}Please wait while we update Centaurus to the latest version.${reset}`),\r\n border(''),\r\n ];\r\n\r\n process.stdout.write(lines.join('\\n') + '\\n');\r\n}\r\n\r\nexport const VersionUpdatePrompt: React.FC<VersionUpdatePromptProps> = ({\r\n currentVersion,\r\n latestVersion,\r\n onComplete\r\n}) => {\r\n const [status, setStatus] = useState<UpdateStatus>('pending');\r\n const [errorMessage, setErrorMessage] = useState<string>('');\r\n const hasStartedRef = useRef(false);\r\n const headerWrittenRef = useRef(false);\r\n const { exit } = useApp();\r\n\r\n // Write static header once this is outside Ink's render cycle\r\n useEffect(() => {\r\n if (headerWrittenRef.current) return;\r\n headerWrittenRef.current = true;\r\n writeStaticUpdateHeader(currentVersion, latestVersion);\r\n }, []);\r\n\r\n // Auto-start installation after a brief delay\r\n useEffect(() => {\r\n if (hasStartedRef.current) return;\r\n hasStartedRef.current = true;\r\n\r\n const timer = setTimeout(() => {\r\n setStatus('installing');\r\n performUpdate();\r\n }, 1500); // Brief delay so user can see the update notification\r\n\r\n return () => clearTimeout(timer);\r\n }, []);\r\n\r\n const performUpdate = () => {\r\n // Determine the package manager - prefer npm as it's most commonly used\r\n const isWindows = process.platform === 'win32';\r\n const npmCmd = isWindows ? 'npm.cmd' : 'npm';\r\n\r\n const installProcess = spawn(\r\n npmCmd,\r\n ['install', '-g', 'centaurus-cli@latest'],\r\n {\r\n shell: true,\r\n stdio: 'pipe', // Capture output for error handling\r\n detached: false\r\n }\r\n );\r\n\r\n let stderrOutput = '';\r\n let stdoutOutput = '';\r\n\r\n if (installProcess.stdout) {\r\n installProcess.stdout.on('data', (data) => {\r\n stdoutOutput += data.toString();\r\n });\r\n }\r\n\r\n if (installProcess.stderr) {\r\n installProcess.stderr.on('data', (data) => {\r\n stderrOutput += data.toString();\r\n });\r\n }\r\n\r\n installProcess.on('error', (err) => {\r\n setStatus('error');\r\n setErrorMessage(`Failed to start update: ${err.message}`);\r\n });\r\n\r\n installProcess.on('close', (code) => {\r\n if (code === 0) {\r\n setStatus('success');\r\n // Wait a moment so user can see success message, then restart\r\n setTimeout(() => {\r\n restartCLI();\r\n }, 1500);\r\n } else {\r\n setStatus('error');\r\n // Parse common error messages for better user feedback\r\n let friendlyError = 'Update failed. Please try manually: npm install -g centaurus-cli@latest';\r\n\r\n if (stderrOutput.includes('EACCES') || stderrOutput.includes('permission')) {\r\n friendlyError = 'Permission denied. Try running with sudo (Linux/Mac) or as Administrator (Windows).';\r\n } else if (stderrOutput.includes('ENOTFOUND') || stderrOutput.includes('network')) {\r\n friendlyError = 'Network error. Please check your internet connection and try again.';\r\n } else if (stderrOutput.includes('ETIMEOUT')) {\r\n friendlyError = 'Connection timed out. Please check your internet connection and try again.';\r\n }\r\n\r\n setErrorMessage(friendlyError);\r\n\r\n // After showing error, continue to chat after a delay\r\n setTimeout(() => {\r\n onComplete();\r\n }, 5000);\r\n }\r\n });\r\n };\r\n\r\n const restartCLI = () => {\r\n // Exit the Ink app first\r\n exit();\r\n\r\n // Clear screen and show success message\r\n process.stdout.write('\\x1b[2J\\x1b[3J\\x1b[H');\r\n logInfo('Update complete! Centaurus has been updated to the latest version.');\r\n logInfo('User should run \"centaurus\" to start the updated CLI.');\r\n\r\n process.exit(0);\r\n };\r\n\r\n // Only the dynamic status line is rendered via Ink this is the only part\r\n // that re-renders on spinner ticks, keeping the banner/header stable.\r\n return (\r\n <Box flexDirection=\"column\">\r\n {status === 'pending' && (\r\n <Text color=\"yellow\">\r\n <Spinner type=\"dots\" /> Preparing to update...\r\n </Text>\r\n )}\r\n\r\n {status === 'installing' && (\r\n <Text color=\"cyan\">\r\n <Spinner type=\"dots\" /> Installing update...\r\n </Text>\r\n )}\r\n\r\n {status === 'success' && (\r\n <Text color=\"green\" bold>✅ Update successful! Restarting Centaurus...</Text>\r\n )}\r\n\r\n {status === 'error' && (\r\n <Box flexDirection=\"column\">\r\n <Text color=\"red\" bold>❌ Update Failed</Text>\r\n <Text color=\"yellow\">{errorMessage}</Text>\r\n <Text dimColor>Continuing to chat in a few seconds...</Text>\r\n </Box>\r\n )}\r\n </Box>\r\n );\r\n};\r\n"],"mappings":"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","names":[]}
@@ -66,11 +66,13 @@ function getGitDiffStats(cwd) {
66
66
  return defaultResult;
67
67
  }
68
68
  try {
69
- const hasHead = execSync("git rev-parse --verify HEAD >/dev/null 2>&1 && echo 1 || echo 0", {
70
- cwd,
71
- encoding: "utf-8",
72
- stdio: ["pipe", "pipe", "ignore"]
73
- }).trim() === "1";
69
+ let hasHead = false;
70
+ try {
71
+ execSync("git rev-parse --verify HEAD", { cwd, stdio: "ignore" });
72
+ hasHead = true;
73
+ } catch {
74
+ hasHead = false;
75
+ }
74
76
  const trackedNumstatCommand = hasHead ? "git diff --numstat --no-ext-diff HEAD" : "git diff --numstat --no-ext-diff --cached";
75
77
  const trackedOutput = execSync(trackedNumstatCommand, {
76
78
  cwd,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/utils/git-stats.ts"],"sourcesContent":["import { execSync } from 'child_process';\r\nimport * as fs from 'fs';\r\nimport * as path from 'path';\r\n\r\nexport interface GitDiffStats {\r\n isGitRepo: boolean;\r\n additions: number;\r\n deletions: number;\r\n filesChanged: number;\r\n}\r\n\r\nfunction escapeForDoubleQuotedShell(value: string): string {\r\n return value\r\n .replace(/\\\\/g, '\\\\\\\\')\r\n .replace(/\"/g, '\\\\\"')\r\n .replace(/\\$/g, '\\\\$')\r\n .replace(/`/g, '\\\\`');\r\n}\r\n\r\nfunction countLinesInText(content: string): number {\r\n if (!content) return 0;\r\n if (content.includes('\\u0000')) return 0;\r\n\r\n let newlineCount = 0;\r\n for (let i = 0; i < content.length; i += 1) {\r\n if (content.charCodeAt(i) === 10) {\r\n newlineCount += 1;\r\n }\r\n }\r\n\r\n return content.endsWith('\\n') ? newlineCount : newlineCount + 1;\r\n}\r\n\r\nfunction countLinesInBuffer(buffer: Buffer): number {\r\n if (buffer.length === 0) return 0;\r\n\r\n let newlineCount = 0;\r\n for (let i = 0; i < buffer.length; i += 1) {\r\n const byte = buffer[i];\r\n if (byte === 0) return 0; // Treat binary files as 0-line additions (matches git numstat behavior better).\r\n if (byte === 10) newlineCount += 1;\r\n }\r\n\r\n return buffer[buffer.length - 1] === 10 ? newlineCount : newlineCount + 1;\r\n}\r\n\r\nfunction parseNumstat(output: string): { additions: number; deletions: number; filesChanged: number } {\r\n let additions = 0;\r\n let deletions = 0;\r\n let filesChanged = 0;\r\n\r\n for (const rawLine of output.split('\\n')) {\r\n if (!rawLine.trim()) continue;\r\n\r\n // Format: \"<additions>\\t<deletions>\\t<path>\"\r\n const parts = rawLine.split('\\t');\r\n if (parts.length < 3) continue;\r\n\r\n const addPart = parts[0].trim();\r\n const delPart = parts[1].trim();\r\n\r\n const parsedAdds = addPart === '-' ? 0 : parseInt(addPart, 10);\r\n const parsedDels = delPart === '-' ? 0 : parseInt(delPart, 10);\r\n\r\n additions += Number.isFinite(parsedAdds) ? parsedAdds : 0;\r\n deletions += Number.isFinite(parsedDels) ? parsedDels : 0;\r\n filesChanged += 1;\r\n }\r\n\r\n return { additions, deletions, filesChanged };\r\n}\r\n\r\n/**\r\n * Check if a directory is inside a git repository\r\n */\r\nexport function isGitRepository(cwd: string): boolean {\r\n try {\r\n execSync('git rev-parse --git-dir', {\r\n cwd,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'ignore'],\r\n });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Get git diff statistics for a directory\r\n * Returns insertions and deletions compared to the working tree\r\n */\r\nexport function getGitDiffStats(cwd: string): GitDiffStats {\r\n const defaultResult: GitDiffStats = {\r\n isGitRepo: false,\r\n additions: 0,\r\n deletions: 0,\r\n filesChanged: 0,\r\n };\r\n\r\n // First check if it's a git repository\r\n if (!isGitRepository(cwd)) {\r\n return defaultResult;\r\n }\r\n\r\n try {\r\n const hasHead = execSync('git rev-parse --verify HEAD >/dev/null 2>&1 && echo 1 || echo 0', {\r\n cwd,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'ignore'],\r\n }).trim() === '1';\r\n\r\n // Track both staged and unstaged net changes against HEAD when available.\r\n const trackedNumstatCommand = hasHead\r\n ? 'git diff --numstat --no-ext-diff HEAD'\r\n : 'git diff --numstat --no-ext-diff --cached';\r\n const trackedOutput = execSync(trackedNumstatCommand, {\r\n cwd,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'ignore'],\r\n }).trim();\r\n const trackedStats = parseNumstat(trackedOutput);\r\n\r\n // Include untracked files (critical for brand-new repos and newly created files).\r\n const untrackedOutput = execSync('git ls-files --others --exclude-standard -z', {\r\n cwd,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'ignore'],\r\n });\r\n const untrackedFiles = untrackedOutput\r\n ? untrackedOutput.split('\\u0000').map(file => file.trim()).filter(Boolean)\r\n : [];\r\n\r\n let untrackedAdditions = 0;\r\n for (const relPath of untrackedFiles) {\r\n const absolutePath = path.join(cwd, relPath);\r\n try {\r\n const stat = fs.statSync(absolutePath);\r\n if (!stat.isFile()) continue;\r\n\r\n const buffer = fs.readFileSync(absolutePath);\r\n untrackedAdditions += countLinesInBuffer(buffer);\r\n } catch {\r\n // Skip unreadable/unavailable files.\r\n }\r\n }\r\n\r\n return {\r\n isGitRepo: true,\r\n additions: trackedStats.additions + untrackedAdditions,\r\n deletions: trackedStats.deletions,\r\n filesChanged: trackedStats.filesChanged + untrackedFiles.length,\r\n };\r\n } catch (error) {\r\n // Git command failed, return default\r\n return {\r\n isGitRepo: true,\r\n additions: 0,\r\n deletions: 0,\r\n filesChanged: 0,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Get git diff stats for a remote directory via SSH/WSL/Docker handler\r\n */\r\nexport async function getRemoteGitDiffStats(\r\n handler: {\r\n executeCommand: (cmd: string) => Promise<{ stdout: string; stderr?: string; exitCode: number }>;\r\n readFile?: (path: string) => Promise<string>;\r\n },\r\n cwd: string\r\n): Promise<GitDiffStats> {\r\n const defaultResult: GitDiffStats = {\r\n isGitRepo: false,\r\n additions: 0,\r\n deletions: 0,\r\n filesChanged: 0,\r\n };\r\n\r\n try {\r\n const escapedCwd = escapeForDoubleQuotedShell(cwd);\r\n\r\n // Check if it's a git repository (do not trust exitCode alone for all handlers).\r\n const checkResult = await handler.executeCommand(`git -C \"${escapedCwd}\" rev-parse --is-inside-work-tree 2>/dev/null`);\r\n if (checkResult.stdout.trim() !== 'true') {\r\n return defaultResult;\r\n }\r\n\r\n const hasHeadResult = await handler.executeCommand(`git -C \"${escapedCwd}\" rev-parse --verify HEAD >/dev/null 2>/dev/null && echo 1 || echo 0`);\r\n const hasHead = hasHeadResult.stdout.trim() === '1';\r\n\r\n const trackedDiffCommand = hasHead\r\n ? `git -C \"${escapedCwd}\" diff --numstat --no-ext-diff HEAD 2>/dev/null`\r\n : `git -C \"${escapedCwd}\" diff --numstat --no-ext-diff --cached 2>/dev/null`;\r\n const trackedResult = await handler.executeCommand(trackedDiffCommand);\r\n const trackedStats = parseNumstat(trackedResult.stdout.trim());\r\n\r\n const untrackedListResult = await handler.executeCommand(`git -C \"${escapedCwd}\" ls-files --others --exclude-standard -z 2>/dev/null`);\r\n const untrackedFiles = untrackedListResult.stdout\r\n .split('\\u0000')\r\n .map(line => line.trim())\r\n .filter(Boolean);\r\n\r\n let untrackedAdditions = 0;\r\n for (const relPath of untrackedFiles) {\r\n const remotePath = `${cwd.replace(/\\/$/, '')}/${relPath}`;\r\n\r\n try {\r\n if (typeof handler.readFile === 'function') {\r\n const content = await handler.readFile(remotePath);\r\n untrackedAdditions += countLinesInText(content);\r\n continue;\r\n }\r\n } catch {\r\n // Fall back to wc below.\r\n }\r\n\r\n try {\r\n const escapedPath = escapeForDoubleQuotedShell(remotePath);\r\n const lineCountResult = await handler.executeCommand(`awk 'END { print NR }' \"${escapedPath}\" 2>/dev/null || echo 0`);\r\n const lineCount = parseInt(lineCountResult.stdout.trim(), 10);\r\n if (Number.isFinite(lineCount)) {\r\n untrackedAdditions += lineCount;\r\n }\r\n } catch {\r\n // Ignore unreadable files.\r\n }\r\n }\r\n\r\n return {\r\n isGitRepo: true,\r\n additions: trackedStats.additions + untrackedAdditions,\r\n deletions: trackedStats.deletions,\r\n filesChanged: trackedStats.filesChanged + untrackedFiles.length,\r\n };\r\n } catch (error) {\r\n return defaultResult;\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/utils/git-stats.ts"],"sourcesContent":["import { execSync } from 'child_process';\r\nimport * as fs from 'fs';\r\nimport * as path from 'path';\r\n\r\nexport interface GitDiffStats {\r\n isGitRepo: boolean;\r\n additions: number;\r\n deletions: number;\r\n filesChanged: number;\r\n}\r\n\r\nfunction escapeForDoubleQuotedShell(value: string): string {\r\n return value\r\n .replace(/\\\\/g, '\\\\\\\\')\r\n .replace(/\"/g, '\\\\\"')\r\n .replace(/\\$/g, '\\\\$')\r\n .replace(/`/g, '\\\\`');\r\n}\r\n\r\nfunction countLinesInText(content: string): number {\r\n if (!content) return 0;\r\n if (content.includes('\\u0000')) return 0;\r\n\r\n let newlineCount = 0;\r\n for (let i = 0; i < content.length; i += 1) {\r\n if (content.charCodeAt(i) === 10) {\r\n newlineCount += 1;\r\n }\r\n }\r\n\r\n return content.endsWith('\\n') ? newlineCount : newlineCount + 1;\r\n}\r\n\r\nfunction countLinesInBuffer(buffer: Buffer): number {\r\n if (buffer.length === 0) return 0;\r\n\r\n let newlineCount = 0;\r\n for (let i = 0; i < buffer.length; i += 1) {\r\n const byte = buffer[i];\r\n if (byte === 0) return 0; // Treat binary files as 0-line additions (matches git numstat behavior better).\r\n if (byte === 10) newlineCount += 1;\r\n }\r\n\r\n return buffer[buffer.length - 1] === 10 ? newlineCount : newlineCount + 1;\r\n}\r\n\r\nfunction parseNumstat(output: string): { additions: number; deletions: number; filesChanged: number } {\r\n let additions = 0;\r\n let deletions = 0;\r\n let filesChanged = 0;\r\n\r\n for (const rawLine of output.split('\\n')) {\r\n if (!rawLine.trim()) continue;\r\n\r\n // Format: \"<additions>\\t<deletions>\\t<path>\"\r\n const parts = rawLine.split('\\t');\r\n if (parts.length < 3) continue;\r\n\r\n const addPart = parts[0].trim();\r\n const delPart = parts[1].trim();\r\n\r\n const parsedAdds = addPart === '-' ? 0 : parseInt(addPart, 10);\r\n const parsedDels = delPart === '-' ? 0 : parseInt(delPart, 10);\r\n\r\n additions += Number.isFinite(parsedAdds) ? parsedAdds : 0;\r\n deletions += Number.isFinite(parsedDels) ? parsedDels : 0;\r\n filesChanged += 1;\r\n }\r\n\r\n return { additions, deletions, filesChanged };\r\n}\r\n\r\n/**\r\n * Check if a directory is inside a git repository\r\n */\r\nexport function isGitRepository(cwd: string): boolean {\r\n try {\r\n execSync('git rev-parse --git-dir', {\r\n cwd,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'ignore'],\r\n });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Get git diff statistics for a directory\r\n * Returns insertions and deletions compared to the working tree\r\n */\r\nexport function getGitDiffStats(cwd: string): GitDiffStats {\r\n const defaultResult: GitDiffStats = {\r\n isGitRepo: false,\r\n additions: 0,\r\n deletions: 0,\r\n filesChanged: 0,\r\n };\r\n\r\n // First check if it's a git repository\r\n if (!isGitRepository(cwd)) {\r\n return defaultResult;\r\n }\r\n\r\n try {\r\n // Determine whether HEAD exists using a try/catch instead of shell redirections\r\n // so the check works on Windows (cmd.exe doesn't understand >/dev/null).\r\n let hasHead = false;\r\n try {\r\n execSync('git rev-parse --verify HEAD', { cwd, stdio: 'ignore' });\r\n hasHead = true;\r\n } catch {\r\n hasHead = false;\r\n }\r\n\r\n // Track both staged and unstaged net changes against HEAD when available.\r\n const trackedNumstatCommand = hasHead\r\n ? 'git diff --numstat --no-ext-diff HEAD'\r\n : 'git diff --numstat --no-ext-diff --cached';\r\n const trackedOutput = execSync(trackedNumstatCommand, {\r\n cwd,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'ignore'],\r\n }).trim();\r\n const trackedStats = parseNumstat(trackedOutput);\r\n\r\n // Include untracked files (critical for brand-new repos and newly created files).\r\n const untrackedOutput = execSync('git ls-files --others --exclude-standard -z', {\r\n cwd,\r\n encoding: 'utf-8',\r\n stdio: ['pipe', 'pipe', 'ignore'],\r\n });\r\n const untrackedFiles = untrackedOutput\r\n ? untrackedOutput.split('\\u0000').map(file => file.trim()).filter(Boolean)\r\n : [];\r\n\r\n let untrackedAdditions = 0;\r\n for (const relPath of untrackedFiles) {\r\n const absolutePath = path.join(cwd, relPath);\r\n try {\r\n const stat = fs.statSync(absolutePath);\r\n if (!stat.isFile()) continue;\r\n\r\n const buffer = fs.readFileSync(absolutePath);\r\n untrackedAdditions += countLinesInBuffer(buffer);\r\n } catch {\r\n // Skip unreadable/unavailable files.\r\n }\r\n }\r\n\r\n return {\r\n isGitRepo: true,\r\n additions: trackedStats.additions + untrackedAdditions,\r\n deletions: trackedStats.deletions,\r\n filesChanged: trackedStats.filesChanged + untrackedFiles.length,\r\n };\r\n } catch (error) {\r\n // Git command failed, return default\r\n return {\r\n isGitRepo: true,\r\n additions: 0,\r\n deletions: 0,\r\n filesChanged: 0,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Get git diff stats for a remote directory via SSH/WSL/Docker handler\r\n */\r\nexport async function getRemoteGitDiffStats(\r\n handler: {\r\n executeCommand: (cmd: string) => Promise<{ stdout: string; stderr?: string; exitCode: number }>;\r\n readFile?: (path: string) => Promise<string>;\r\n },\r\n cwd: string\r\n): Promise<GitDiffStats> {\r\n const defaultResult: GitDiffStats = {\r\n isGitRepo: false,\r\n additions: 0,\r\n deletions: 0,\r\n filesChanged: 0,\r\n };\r\n\r\n try {\r\n const escapedCwd = escapeForDoubleQuotedShell(cwd);\r\n\r\n // Check if it's a git repository (do not trust exitCode alone for all handlers).\r\n const checkResult = await handler.executeCommand(`git -C \"${escapedCwd}\" rev-parse --is-inside-work-tree 2>/dev/null`);\r\n if (checkResult.stdout.trim() !== 'true') {\r\n return defaultResult;\r\n }\r\n\r\n const hasHeadResult = await handler.executeCommand(`git -C \"${escapedCwd}\" rev-parse --verify HEAD >/dev/null 2>/dev/null && echo 1 || echo 0`);\r\n const hasHead = hasHeadResult.stdout.trim() === '1';\r\n\r\n const trackedDiffCommand = hasHead\r\n ? `git -C \"${escapedCwd}\" diff --numstat --no-ext-diff HEAD 2>/dev/null`\r\n : `git -C \"${escapedCwd}\" diff --numstat --no-ext-diff --cached 2>/dev/null`;\r\n const trackedResult = await handler.executeCommand(trackedDiffCommand);\r\n const trackedStats = parseNumstat(trackedResult.stdout.trim());\r\n\r\n const untrackedListResult = await handler.executeCommand(`git -C \"${escapedCwd}\" ls-files --others --exclude-standard -z 2>/dev/null`);\r\n const untrackedFiles = untrackedListResult.stdout\r\n .split('\\u0000')\r\n .map(line => line.trim())\r\n .filter(Boolean);\r\n\r\n let untrackedAdditions = 0;\r\n for (const relPath of untrackedFiles) {\r\n const remotePath = `${cwd.replace(/\\/$/, '')}/${relPath}`;\r\n\r\n try {\r\n if (typeof handler.readFile === 'function') {\r\n const content = await handler.readFile(remotePath);\r\n untrackedAdditions += countLinesInText(content);\r\n continue;\r\n }\r\n } catch {\r\n // Fall back to wc below.\r\n }\r\n\r\n try {\r\n const escapedPath = escapeForDoubleQuotedShell(remotePath);\r\n const lineCountResult = await handler.executeCommand(`awk 'END { print NR }' \"${escapedPath}\" 2>/dev/null || echo 0`);\r\n const lineCount = parseInt(lineCountResult.stdout.trim(), 10);\r\n if (Number.isFinite(lineCount)) {\r\n untrackedAdditions += lineCount;\r\n }\r\n } catch {\r\n // Ignore unreadable files.\r\n }\r\n }\r\n\r\n return {\r\n isGitRepo: true,\r\n additions: trackedStats.additions + untrackedAdditions,\r\n deletions: trackedStats.deletions,\r\n filesChanged: trackedStats.filesChanged + untrackedFiles.length,\r\n };\r\n } catch (error) {\r\n return defaultResult;\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
@@ -0,0 +1,25 @@
1
+ import { render } from "ink";
2
+ import { Writable } from "stream";
3
+ function renderToString(element) {
4
+ let output = "";
5
+ const fakeStdout = new Writable({
6
+ write(chunk, _encoding, callback) {
7
+ output += chunk.toString();
8
+ callback();
9
+ }
10
+ });
11
+ fakeStdout.columns = process.stdout.columns || 120;
12
+ fakeStdout.rows = process.stdout.rows || 40;
13
+ const { unmount, cleanup } = render(element, {
14
+ stdout: fakeStdout,
15
+ patchConsole: false,
16
+ exitOnCtrlC: false
17
+ });
18
+ unmount();
19
+ cleanup();
20
+ return output.replace(/\x1b\[\?25[lh]/g, "").replace(/\x1b\[J/g, "").trimEnd();
21
+ }
22
+ export {
23
+ renderToString
24
+ };
25
+ //# sourceMappingURL=ink-static-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/ink-static-render.ts"],"sourcesContent":["/**\r\n * Render an Ink (React) element to a string.\r\n *\r\n * This spins up a short-lived Ink instance whose stdout is a custom\r\n * writable stream that captures output in memory. The instance is\r\n * immediately unmounted so it never enters the event loop, and the\r\n * captured string is returned.\r\n *\r\n * Use this for \"print once\" content (e.g. the welcome banner) that\r\n * should not participate in a later interactive Ink render tree.\r\n */\r\n\r\nimport { render } from 'ink';\r\nimport { Writable } from 'stream';\r\n\r\nexport function renderToString(element: React.ReactElement): string {\r\n let output = '';\r\n\r\n // Create a writable stream that accumulates chunks in memory\r\n const fakeStdout = new Writable({\r\n write(chunk, _encoding, callback) {\r\n output += chunk.toString();\r\n callback();\r\n },\r\n });\r\n\r\n // Ink needs `.columns` on the stream to compute layout widths.\r\n // Mirror the real terminal width.\r\n (fakeStdout as any).columns = process.stdout.columns || 120;\r\n (fakeStdout as any).rows = process.stdout.rows || 40;\r\n\r\n const { unmount, cleanup } = render(element, {\r\n stdout: fakeStdout as any,\r\n patchConsole: false,\r\n exitOnCtrlC: false,\r\n });\r\n\r\n unmount();\r\n cleanup();\r\n\r\n // Ink wraps output in cursor-hide / cursor-show escape sequences\r\n // and may include trailing clear sequences. Strip them so we get\r\n // just the visible content.\r\n return output\r\n .replace(/\\x1b\\[\\?25[lh]/g, '') // cursor hide / show\r\n .replace(/\\x1b\\[J/g, '') // erase-to-end\r\n .trimEnd();\r\n}\r\n"],"mappings":"AAYA,SAAS,cAAc;AACvB,SAAS,gBAAgB;AAElB,SAAS,eAAe,SAAqC;AAChE,MAAI,SAAS;AAGb,QAAM,aAAa,IAAI,SAAS;AAAA,IAC5B,MAAM,OAAO,WAAW,UAAU;AAC9B,gBAAU,MAAM,SAAS;AACzB,eAAS;AAAA,IACb;AAAA,EACJ,CAAC;AAID,EAAC,WAAmB,UAAU,QAAQ,OAAO,WAAW;AACxD,EAAC,WAAmB,OAAO,QAAQ,OAAO,QAAQ;AAElD,QAAM,EAAE,SAAS,QAAQ,IAAI,OAAO,SAAS;AAAA,IACzC,QAAQ;AAAA,IACR,cAAc;AAAA,IACd,aAAa;AAAA,EACjB,CAAC;AAED,UAAQ;AACR,UAAQ;AAKR,SAAO,OACF,QAAQ,mBAAmB,EAAE,EAC7B,QAAQ,YAAY,EAAE,EACtB,QAAQ;AACjB;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "centaurus-cli",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "A powerful command-line AI coding assistant with Google Gemini support",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",