Skip to content

Commit d549f3d

Browse files
committed
feat: authentication de-dupe and email capture
1 parent ae84da3 commit d549f3d

File tree

5 files changed

+332
-16
lines changed

5 files changed

+332
-16
lines changed

cmd/assistant.go

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ package cmd
2020

2121
import (
2222
"context"
23+
"encoding/json"
2324
"fmt"
25+
"io"
26+
"os"
2427
"strings"
2528

2629
"github.com/AlecAivazis/survey/v2"
@@ -224,21 +227,70 @@ var updateAssistantCmd = &cobra.Command{
224227
Short: "Update an existing assistant",
225228
Long: `Update an assistant's configuration.
226229
227-
Complex updates involving voice models, tools, or advanced settings
228-
are best done through the Vapi dashboard at https://dashboard.vapi.ai`,
230+
You can provide raw JSON via --json or --file to quickly copy configs across orgs.
231+
Examples:
232+
vapi assistant update <id> --file assistant.json
233+
cat assistant.json | vapi assistant update <id> --json -
234+
vapi assistant update <id> --json '{"name":"New Name"}'
235+
236+
Complex updates can also be done via the Vapi dashboard at https://dashboard.vapi.ai`,
229237
Args: cobra.ExactArgs(1),
230238
RunE: analytics.TrackCommandWrapper("assistant", "update", func(cmd *cobra.Command, args []string) error {
231239
assistantID := args[0]
232240

233-
fmt.Printf("📝 Update assistant: %s\n", assistantID)
234-
fmt.Println()
235-
fmt.Println("Assistant updates are best done through the Vapi dashboard where you can:")
236-
fmt.Println("- Configure model settings (GPT-4, Claude, etc.)")
237-
fmt.Println("- Select and customize voices")
238-
fmt.Println("- Set up tools and functions")
239-
fmt.Println("- Configure advanced behaviors")
240-
fmt.Println()
241-
fmt.Println("Visit: https://dashboard.vapi.ai/assistants")
241+
// Flags
242+
jsonStr, _ := cmd.Flags().GetString("json")
243+
filePath, _ := cmd.Flags().GetString("file")
244+
245+
if jsonStr == "" && filePath == "" {
246+
return fmt.Errorf("provide --json or --file for update payload")
247+
}
248+
249+
var payloadBytes []byte
250+
251+
if filePath != "" {
252+
b, err := os.ReadFile(filePath)
253+
if err != nil {
254+
return fmt.Errorf("failed to read --file: %w", err)
255+
}
256+
payloadBytes = b
257+
} else {
258+
// jsonStr provided
259+
if jsonStr == "-" {
260+
// read from stdin
261+
b, err := io.ReadAll(os.Stdin)
262+
if err != nil {
263+
return fmt.Errorf("failed to read stdin: %w", err)
264+
}
265+
payloadBytes = b
266+
} else {
267+
payloadBytes = []byte(jsonStr)
268+
}
269+
}
270+
271+
// Basic validation
272+
var tmp map[string]interface{}
273+
if err := json.Unmarshal(payloadBytes, &tmp); err != nil {
274+
return fmt.Errorf("invalid JSON: %w", err)
275+
}
276+
277+
ctx := context.Background()
278+
279+
// Use low-level raw request helper
280+
respBody, err := vapiClient.DoRawJSON(ctx, "PATCH", fmt.Sprintf("/assistants/%s", assistantID), payloadBytes)
281+
if err != nil {
282+
return fmt.Errorf("failed to update assistant: %w", err)
283+
}
284+
285+
fmt.Println("✅ Assistant updated successfully")
286+
if name, ok := respBody["name"].(string); ok && name != "" {
287+
fmt.Printf("Name: %s\n", name)
288+
}
289+
290+
// Print the updated assistant JSON
291+
if err := output.PrintJSON(respBody); err != nil {
292+
return fmt.Errorf("failed to display response: %w", err)
293+
}
242294

243295
return nil
244296
}),
@@ -291,4 +343,8 @@ func init() {
291343
assistantCmd.AddCommand(getAssistantCmd)
292344
assistantCmd.AddCommand(updateAssistantCmd)
293345
assistantCmd.AddCommand(deleteAssistantCmd)
346+
347+
// Flags for update
348+
updateAssistantCmd.Flags().String("json", "", "Raw JSON payload string or '-' to read from stdin")
349+
updateAssistantCmd.Flags().String("file", "", "Path to JSON file with assistant payload")
294350
}

cmd/auth.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,45 @@ This secure authentication flow:
5454
fmt.Println("🔐 Authenticating with Vapi...")
5555
fmt.Println()
5656

57+
// Read optional flags for labeling
58+
labelFlag, _ := cmd.Flags().GetString("label")
59+
emailFlag, _ := cmd.Flags().GetString("email") // alias, for backward-compat
60+
orgFlag, _ := cmd.Flags().GetString("org")
61+
5762
// Start the browser-based authentication flow
5863
// The Login() function handles saving the API key
5964
if err := auth.Login(); err != nil {
6065
return err
6166
}
6267

68+
// Optionally override label/org with flags if provided
69+
cfg, err := config.LoadConfig()
70+
if err == nil && cfg.ActiveAccount != "" {
71+
acc := cfg.Accounts[cfg.ActiveAccount]
72+
changed := false
73+
74+
// Apply flags if provided
75+
if labelFlag != "" {
76+
acc.Label = labelFlag
77+
changed = true
78+
} else if emailFlag != "" { // backward-compat: set label from email flag
79+
acc.Label = emailFlag
80+
changed = true
81+
}
82+
if orgFlag != "" {
83+
acc.Organization = orgFlag
84+
changed = true
85+
}
86+
87+
// No more interactive prompt - the dashboard now provides label/email
88+
89+
// Persist if any changes
90+
if changed {
91+
cfg.Accounts[cfg.ActiveAccount] = acc
92+
_ = config.SaveConfig(cfg)
93+
}
94+
}
95+
6396
fmt.Println("\nYou can now use all Vapi CLI commands.")
6497
fmt.Println("• List assistants: vapi assistant list")
6598
fmt.Println("• View call history: vapi call list")
@@ -175,6 +208,12 @@ which account you're currently using.`,
175208
if account.Organization != "" {
176209
fmt.Printf(" - %s", account.Organization)
177210
}
211+
// Prefer Label; fallback to Email if present
212+
if account.Label != "" {
213+
fmt.Printf(" <%s>", account.Label)
214+
} else if account.Email != "" {
215+
fmt.Printf(" <%s>", account.Email)
216+
}
178217
if account.LoginTime != "" {
179218
fmt.Printf(" (logged in: %s)", account.LoginTime[:10]) // Show just the date
180219
}
@@ -272,6 +311,12 @@ This is useful when working with multiple organizations or environments.`,
272311
if account.Organization != "" {
273312
displayName = fmt.Sprintf("%s (%s)", accountName, account.Organization)
274313
}
314+
// Prefer Label; fallback to Email if present
315+
if account.Label != "" {
316+
displayName = fmt.Sprintf("%s <%s>", displayName, account.Label)
317+
} else if account.Email != "" {
318+
displayName = fmt.Sprintf("%s <%s>", displayName, account.Email)
319+
}
275320
if accountName == cfg.ActiveAccount {
276321
displayName += " ✓ (currently active)"
277322
}
@@ -314,6 +359,11 @@ This is useful when working with multiple organizations or environments.`,
314359
if account.Organization != "" {
315360
fmt.Printf(" - %s", account.Organization)
316361
}
362+
if account.Label != "" {
363+
fmt.Printf(" <%s>", account.Label)
364+
} else if account.Email != "" {
365+
fmt.Printf(" <%s>", account.Email)
366+
}
317367
fmt.Println()
318368
}
319369
return fmt.Errorf("account '%s' not found", targetAccount)
@@ -332,6 +382,11 @@ This is useful when working with multiple organizations or environments.`,
332382
if account.Organization != "" {
333383
fmt.Printf(" (%s)", account.Organization)
334384
}
385+
if account.Label != "" {
386+
fmt.Printf(" <%s>", account.Label)
387+
} else if account.Email != "" {
388+
fmt.Printf(" <%s>", account.Email)
389+
}
335390
fmt.Println()
336391
return nil
337392
}),
@@ -453,4 +508,9 @@ func init() {
453508

454509
// Add auth command to root
455510
rootCmd.AddCommand(authCmd)
511+
512+
// Flags for login labeling
513+
authLoginCmd.Flags().String("label", "", "Override the label for this account (default: your email from dashboard)")
514+
authLoginCmd.Flags().String("email", "", "Alias for --label")
515+
authLoginCmd.Flags().String("org", "", "Override the organization name for this account")
456516
}

0 commit comments

Comments
 (0)