-
Notifications
You must be signed in to change notification settings - Fork 10
Expand file tree
/
Copy pathroot.go
More file actions
265 lines (223 loc) Β· 8.62 KB
/
Copy pathroot.go
File metadata and controls
265 lines (223 loc) Β· 8.62 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/*
Copyright Β© 2025 Vapi, Inc.
Licensed under the MIT License (the "License");
you may not use this file except in compliance with the License.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Authors:
Dan Goosewin <dan@vapi.ai>
*/
package cmd
import (
"fmt"
"os"
"time"
"github.com/charmbracelet/lipgloss"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/VapiAI/cli/pkg/analytics"
"github.com/VapiAI/cli/pkg/client"
"github.com/VapiAI/cli/pkg/config"
)
var (
cfgFile string
vapiClient *client.VapiClient
bannerShown bool
)
// ASCII art banner
var asciiArt = `
0000000 0000000 0000000 00000000000000000 0000000
00000000000 00000000000 0000000000 0000000000000000000000 00000000000
00000000000 000000000000 000000000000 000000000000000000000000 00000000000
0000000000000 0000000000000 00000000000000 000000000000000000000000 00000000000
0000000000000 000000000000 0000000000000000 0000000000000000000000000 00000000000
00000000000000000000000000 000000000000000000 000000000000000000000000 00000000000
000000000000000000000000 00000000000000000000 000000000000000000000000 00000000000
0000000000000000000000 000000000000000000000 00000000000000000000000 00000000000
00000000000000000000 00000000000000000000000 000000000000000000000 00000000000
000000000000000000 0000000000000000000000000 0000000000000000 00000000000
0000000000000000 000000000000000000000000000 000000000000 00000000000
00000000000000 00000000000000000000000000000 000000000000 00000000000
000000000000 00000000000000000000000000000 000000000000 00000000000
0000000000 00000000000000000000000000000 0000000000 00000000000
00000000 0000000000000000000000000 00000000 0000000
`
// Display banner with styling
func displayBanner() {
if bannerShown {
return
}
bannerShown = true
// Create styles
titleStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#62F6B5")). // Brand green color
Bold(true)
subtitleStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#A8F5D7")). // Light green
Italic(true)
versionStyle := lipgloss.NewStyle().
Foreground(lipgloss.Color("#6B7280")). // Gray
Faint(true)
// Print banner
fmt.Println(titleStyle.Render(asciiArt))
fmt.Println(subtitleStyle.Render("Voice AI for developers"))
fmt.Println(versionStyle.Render("v" + version))
fmt.Println()
}
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "vapi",
Short: "Official Vapi CLI - Build voice AI applications with ease",
Long: `π€ Vapi CLI - The official command-line interface for Vapi
Build, deploy, and manage voice AI applications with the power of Vapi's platform.
From simple voice assistants to complex phone call workflows, the Vapi CLI provides
everything you need to create production-ready voice AI solutions.
π Getting Started:
vapi login # Authenticate with your Vapi account
vapi init # Initialize Vapi in your project
vapi mcp setup # Set up IDE integration for better development
π― Common Tasks:
vapi assistant list # View your voice assistants
vapi call create # Make outbound calls programmatically
vapi phone list # Manage phone numbers
vapi logs # View call logs and system events
π Documentation: https://docs.vapi.ai
π¬ Community: https://discord.gg/vapi
π Issues: https://github.com/VapiAI/cli/issues`,
RunE: func(cmd *cobra.Command, args []string) error {
// Track command execution
startTime := time.Now()
defer func() {
duration := time.Since(startTime)
analytics.TrackCommand("vapi", "", true, duration, "")
}()
// Check if --version flag is set
if versionFlag, _ := cmd.Flags().GetBool("version"); versionFlag {
fmt.Printf("vapi version %s\n", version)
analytics.TrackEvent("version_displayed", map[string]interface{}{
"version": version,
})
return nil
}
// Always display banner when running root command without subcommands
displayBanner()
// Display help by default when no subcommand is provided
return cmd.Help()
},
}
func init() {
cobra.OnInitialize(initConfig)
// Set up PersistentPreRunE here to avoid initialization cycle
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
// Skip validation for root command with no subcommands (just showing help)
if cmd.Parent() == nil && len(args) == 0 && len(cmd.Commands()) > 0 {
return nil
}
// Skip API key validation for commands that don't need it
skipAuthCommands := []string{"login", "config", "init", "completion", "help", "version", "update", "mcp", "auth", "manual"}
// Only check the first level command name! If we don't do this, we erroneously skip the API key validation for
// commands like "assistant update", which actually need it.
cmdName := cmd.Name()
if cmd.Parent() != nil && cmd.Root().Name() != cmd.Parent().Name() {
cmdName = cmd.Parent().Name()
}
for _, skipCmd := range skipAuthCommands {
if cmdName == skipCmd {
return nil
}
}
// Validate API key is configured
apiKey := viper.GetString("api_key")
if apiKey == "" {
printAuthPrompt()
return fmt.Errorf("not authenticated")
}
// Initialize the Vapi client for API commands
var err error
vapiClient, err = client.NewVapiClient(apiKey)
if err != nil {
return fmt.Errorf("failed to initialize Vapi client: %w", err)
}
return nil
}
// Set up PersistentPostRunE for cleanup
rootCmd.PersistentPostRunE = func(cmd *cobra.Command, args []string) error {
// Ensure analytics client is properly closed
analytics.Close()
return nil
}
// Global flag for config file location
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./.vapi-cli.yaml or $HOME/.vapi-cli.yaml)")
// Global flag for API key override
rootCmd.PersistentFlags().String("api-key", "", "Vapi API key")
if err := viper.BindPFlag("api_key", rootCmd.PersistentFlags().Lookup("api-key")); err != nil {
fmt.Printf("Warning: failed to bind api-key flag: %v\n", err)
}
// Add version flag
rootCmd.Flags().BoolP("version", "v", false, "Print version information")
}
// Execute runs the root command - this is the main entry point
func Execute() {
// Execute the CLI
if err := rootCmd.Execute(); err != nil {
analytics.TrackError(err.Error(), map[string]interface{}{
"command": "root",
})
analytics.Close()
os.Exit(1)
}
// Close analytics on successful completion
analytics.Close()
}
// Initialize viper configuration from file and environment
func initConfig() {
if cfgFile != "" {
// Use config file from the flag
viper.SetConfigFile(cfgFile)
} else {
// Search for config in current directory first, then home
viper.AddConfigPath(".")
viper.AddConfigPath("$HOME")
viper.SetConfigType("yaml")
viper.SetConfigName(".vapi-cli")
}
// Environment variables take precedence
viper.AutomaticEnv()
viper.SetEnvPrefix("VAPI")
// Read config file if it exists (ignore errors)
if err := viper.ReadInConfig(); err == nil {
// Config file was found and read successfully
if viper.GetBool("debug") {
fmt.Printf("Using config file: %s\n", viper.ConfigFileUsed())
}
}
// Load configuration and set global config
cfg, err := config.LoadConfig()
if err != nil {
// Don't fail the CLI if config loading fails
if viper.GetBool("debug") {
fmt.Printf("Warning: failed to load config: %v\n", err)
}
} else {
config.SetConfig(cfg)
}
// Initialize analytics after config is loaded
analytics.Initialize()
}
// Display instructions for authentication
func printAuthPrompt() {
fmt.Println("π Authentication required")
fmt.Println()
fmt.Println("You need to authenticate with Vapi to use this command.")
fmt.Println()
fmt.Println("Run: vapi login")
fmt.Println()
fmt.Println("Or set your API key manually:")
fmt.Println(" export VAPI_API_KEY=your_api_key_here")
fmt.Println()
}