@@ -27,6 +27,8 @@ import (
2727 "github.com/spf13/cobra"
2828 "github.com/spf13/cobra/doc"
2929
30+ "os/exec"
31+
3032 versionpkg "github.com/VapiAI/cli/pkg/version"
3133)
3234
@@ -41,6 +43,184 @@ var manualCmd = &cobra.Command{
4143
4244var manualOutputDir string
4345
46+ // installManPagesCmd installs manual pages for the current user
47+ var installManPagesCmd = & cobra.Command {
48+ Use : "install-man-pages" ,
49+ Short : "Install manual pages for the Vapi CLI" ,
50+ Long : `Install Unix manual pages for the Vapi CLI to enable 'man vapi' command.
51+
52+ This command will:
53+ 1. Generate the latest manual pages for all commands
54+ 2. Install them to the appropriate system location
55+ 3. Update the manual database
56+
57+ Requires administrative privileges for system-wide installation.` ,
58+ RunE : installManualPages ,
59+ }
60+
61+ func installManualPages (cmd * cobra.Command , args []string ) error {
62+ fmt .Printf ("π Installing Vapi CLI manual pages...\n " )
63+
64+ // Create a temporary directory for generated pages
65+ tmpDir := filepath .Join (os .TempDir (), "vapi-man-pages" )
66+ if err := os .MkdirAll (tmpDir , 0o750 ); err != nil {
67+ return fmt .Errorf ("failed to create temporary directory: %w" , err )
68+ }
69+ defer os .RemoveAll (tmpDir ) // Clean up temp directory
70+
71+ // Generate man pages
72+ fmt .Printf (" π Generating manual pages...\n " )
73+
74+ header := & doc.GenManHeader {
75+ Title : "VAPI" ,
76+ Section : "1" ,
77+ Source : fmt .Sprintf ("Vapi CLI %s" , versionpkg .Get ()),
78+ Manual : "Vapi CLI Manual" ,
79+ Date : & []time.Time {time .Now ()}[0 ],
80+ }
81+
82+ if err := doc .GenManTree (rootCmd , header , tmpDir ); err != nil {
83+ return fmt .Errorf ("failed to generate man pages: %w" , err )
84+ }
85+
86+ // Find appropriate installation directory
87+ manDir := getManPageInstallDir ()
88+ if manDir == "" {
89+ return fmt .Errorf ("could not find suitable man page directory" )
90+ }
91+
92+ // Check if directory exists and is writable
93+ if err := ensureManDirWritable (manDir ); err != nil {
94+ fmt .Printf (" β οΈ Need administrative privileges to install to %s\n " , manDir )
95+ fmt .Printf (" π‘ Suggestions:\n " )
96+ fmt .Printf (" β’ Run with sudo: sudo vapi install-man-pages\n " )
97+ fmt .Printf (" β’ Or manually copy files from %s to %s\n " , tmpDir , manDir )
98+ return err
99+ }
100+
101+ // Copy man pages to installation directory
102+ fmt .Printf (" π Installing to %s...\n " , manDir )
103+
104+ files , err := filepath .Glob (filepath .Join (tmpDir , "*.1" ))
105+ if err != nil {
106+ return fmt .Errorf ("failed to find generated man pages: %w" , err )
107+ }
108+
109+ for _ , file := range files {
110+ fileName := filepath .Base (file )
111+ destPath := filepath .Join (manDir , fileName )
112+
113+ if err := copyFile (file , destPath ); err != nil {
114+ return fmt .Errorf ("failed to copy %s: %w" , fileName , err )
115+ }
116+
117+ // Set appropriate permissions
118+ if err := os .Chmod (destPath , 0o644 ); err != nil {
119+ fmt .Printf (" β οΈ Warning: failed to set permissions on %s\n " , fileName )
120+ }
121+ }
122+
123+ // Update man database
124+ fmt .Printf (" π Updating manual database...\n " )
125+ if err := updateManDatabase (); err != nil {
126+ fmt .Printf (" β οΈ Warning: failed to update man database: %v\n " , err )
127+ fmt .Printf (" π‘ You may need to run 'sudo mandb' manually\n " )
128+ }
129+
130+ fmt .Printf (" β
Installed %d manual page(s)\n " , len (files ))
131+ fmt .Println ()
132+ fmt .Println ("π Manual pages installed successfully!" )
133+ fmt .Println ()
134+ fmt .Println ("π Usage:" )
135+ fmt .Println (" man vapi # Main CLI manual" )
136+ fmt .Println (" man vapi-call # Call management" )
137+ fmt .Println (" man vapi-mcp # MCP integration" )
138+ fmt .Println (" man vapi-assistant# Assistant management" )
139+ fmt .Println ()
140+ fmt .Println ("π‘ Try 'man vapi' to test the installation!" )
141+
142+ return nil
143+ }
144+
145+ func getManPageInstallDir () string {
146+ // Check common man page directories in order of preference
147+ candidates := []string {
148+ "/usr/local/share/man/man1" , // Most common for user-installed tools
149+ "/opt/homebrew/share/man/man1" , // Homebrew on Apple Silicon
150+ "/usr/share/man/man1" , // System-wide
151+ }
152+
153+ for _ , dir := range candidates {
154+ if stat , err := os .Stat (dir ); err == nil && stat .IsDir () {
155+ return dir
156+ }
157+ }
158+
159+ return ""
160+ }
161+
162+ func ensureManDirWritable (dir string ) error {
163+ // Check if directory exists
164+ stat , err := os .Stat (dir )
165+ if err != nil {
166+ // Try to create the directory
167+ if err := os .MkdirAll (dir , 0o755 ); err != nil {
168+ return fmt .Errorf ("directory does not exist and cannot be created: %w" , err )
169+ }
170+ return nil
171+ }
172+
173+ if ! stat .IsDir () {
174+ return fmt .Errorf ("path exists but is not a directory" )
175+ }
176+
177+ // Test write permissions by creating a temporary file
178+ testFile := filepath .Join (dir , ".vapi-test-write" )
179+ if file , err := os .Create (testFile ); err != nil {
180+ return fmt .Errorf ("directory is not writable: %w" , err )
181+ } else {
182+ file .Close ()
183+ os .Remove (testFile ) // Clean up test file
184+ }
185+
186+ return nil
187+ }
188+
189+ func copyFile (src , dst string ) error {
190+ srcFile , err := os .Open (src )
191+ if err != nil {
192+ return err
193+ }
194+ defer srcFile .Close ()
195+
196+ dstFile , err := os .Create (dst )
197+ if err != nil {
198+ return err
199+ }
200+ defer dstFile .Close ()
201+
202+ _ , err = srcFile .WriteTo (dstFile )
203+ return err
204+ }
205+
206+ func updateManDatabase () error {
207+ // Try to update the man database
208+ commands := [][]string {
209+ {"mandb" }, // Most common
210+ {"makewhatis" }, // Some systems
211+ {"catman" , "-w" }, // Alternative
212+ }
213+
214+ for _ , cmdArgs := range commands {
215+ cmd := exec .Command (cmdArgs [0 ], cmdArgs [1 :]... )
216+ if err := cmd .Run (); err == nil {
217+ return nil // Success
218+ }
219+ }
220+
221+ return fmt .Errorf ("no suitable man database update command found" )
222+ }
223+
44224func generateManualPages (cmd * cobra.Command , args []string ) error {
45225 fmt .Printf ("π Generating manual pages for Vapi CLI v%s...\n " , versionpkg .Get ())
46226
@@ -95,4 +275,5 @@ func init() {
95275 "Output directory for generated manual pages" )
96276
97277 rootCmd .AddCommand (manualCmd )
278+ rootCmd .AddCommand (installManPagesCmd )
98279}
0 commit comments