]> git.giacomolli.uk - onechat/blob - main.go
Pass caller contexts through WhatsApp callbacks
[onechat] / main.go
1 // Command onechat runs the agent daemon and its terminal client.
2 package main
3
4 import (
5 "context"
6 "fmt"
7 "os"
8 "os/signal"
9 "strings"
10 "syscall"
11 )
12
13 // usage is the help text of the command.
14 const usage = `Usage: onechat [COMMAND]
15
16 Commands:
17 backup FILE archive the running daemon data
18 restore FILE restore into an empty data directory
19 chat talk to the agent in this terminal (default)
20 daemon run the agent, Telegram, and WhatsApp
21 import [-n] [--agent NAME] ZIP...
22 import WhatsApp history (stop the daemon first)
23 -n print the plan without model calls
24 --agent NAME import into an existing direct child (default: root)
25 login log in to ChatGPT with a device code
26 probe LOG COUNT [BUILDS] measure the compactor on a log, with real calls
27 version show the build version
28 help show this help
29
30 Chat needs a running daemon. ONECHAT_DIR sets the data directory.
31 In chat, /image PATH [caption] sends a JPEG, PNG, or WebP image up to 20 MB.
32 Quote PATH with single or double quotes when it contains spaces.
33 `
34
35 // version is set by the package build and stays dev for direct Go builds.
36 var version = "dev"
37
38 func main() {
39 err := run()
40 if err != nil {
41 fmt.Fprintln(os.Stderr, "onechat:", err)
42 os.Exit(1)
43 }
44 }
45
46 func run() error {
47 ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
48 defer cancel()
49
50 args := os.Args[1:]
51 switch {
52 case len(args) == 1 && (args[0] == "help" || args[0] == "--help" || args[0] == "-h"):
53 _, err := fmt.Print(usage)
54 if err != nil {
55 return fmt.Errorf("write command help: %w", err)
56 }
57 case len(args) == 1 && args[0] == "version":
58 _, err := fmt.Println(version)
59 if err != nil {
60 return fmt.Errorf("write build version: %w", err)
61 }
62 case len(args) == 1 && args[0] == "login":
63 err := chatgptLogin(ctx, os.Stdout)
64 if err != nil {
65 return err
66 }
67
68 // A signal is the normal stop of the daemon and the client, so their
69 // result does not include ctx.Err().
70 case len(args) == 1 && args[0] == "daemon":
71 return daemon(ctx, os.Stdout)
72 case len(args) == 0 || len(args) == 1 && args[0] == "chat":
73 return terminal(ctx, os.Stdin, os.Stdout)
74 case len(args) == 2 && args[0] == "backup":
75 return backup(ctx, args[1], os.Stdout)
76 case len(args) == 2 && args[0] == "restore":
77 return restore(ctx, args[1], os.Stdout)
78 case len(args) > 0 && args[0] == "import":
79 return whatsappImport(ctx, args[1:], os.Stdout)
80 case len(args) > 0 && args[0] == "probe":
81 err := probe(ctx, args[1:], os.Stdout)
82 if err != nil {
83 return err
84 }
85 default:
86 return fmt.Errorf("unknown command %q\n\n%s", strings.Join(args, " "), strings.TrimSuffix(usage, "\n"))
87 }
88
89 return ctx.Err()
90 }