diff --git a/NOTES.md b/NOTES.md
new file mode 100644
index 0000000000000000000000000000000000000000..9ce89dc3f071adfc4d3def1017b0a46515d48f69
--- /dev/null
+++ b/NOTES.md
@@ -0,0 +1,11 @@
+# Development notes
+
+These are the development notes for the Go version.
+
+The idea is to use the help files to implement BULLETIN.
+
+## Module links
+
+  * cli ([docs](https://cli.urfave.org/v3/getting-started/), [github](https://pkg.go.dev/github.com/urfave/cli/v3))
+  * readline ([docs](https://pkg.go.dev/github.com/chzyer/readline), [github](https://github.com/chzyer/readline))
+  * xdg ([docs](https://pkg.go.dev/github.com/adrg/xdg), [github](https://github.com/adrg/xdg))
diff --git a/accounts/accounts.go b/accounts/accounts.go
index 32412dd489ffa0b5714fbae70a4f63c2f17d24ac..beb1c48f95d204ccc73930ceb77c96dfdd57525a 100644
--- a/accounts/accounts.go
+++ b/accounts/accounts.go
@@ -10,3 +10,8 @@ func Verify(acc string) error {
 	}
 	return nil
 }
+
+// IsAdmin returns true if the user is an admin
+func IsAdmin(acc string) bool {
+	return false
+}
diff --git a/go.mod b/go.mod
index a82526a6e502dfec887a738223b22fb03a5595ff..a7c9849b48a2fcb5ed8f42bdcc3004683d46f4c7 100644
--- a/go.mod
+++ b/go.mod
@@ -2,4 +2,8 @@ module git.lyda.ie/kevin/bulletin
 
 go 1.24.2
 
-require github.com/urfave/cli/v3 v3.3.2 // indirect
+require (
+	github.com/chzyer/readline v1.5.1 // indirect
+	github.com/urfave/cli/v3 v3.3.2 // indirect
+	golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
+)
diff --git a/go.sum b/go.sum
index 80dc36d5fb7c9d6272c40569bcad852a0bb49e34..e3a5fccaaa4e63c632e27b59b7223a1c62204460 100644
--- a/go.sum
+++ b/go.sum
@@ -1,2 +1,8 @@
+github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
+github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
+github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
+github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
 github.com/urfave/cli/v3 v3.3.2 h1:BYFVnhhZ8RqT38DxEYVFPPmGFTEf7tJwySTXsVRrS/o=
 github.com/urfave/cli/v3 v3.3.2/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
+golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/main.go b/main.go
index 6df8ee6c8e51616777344a7807dc47ed67e98369..be02f5d8c5866eed92d45de4dc64730154836c59 100644
--- a/main.go
+++ b/main.go
@@ -8,6 +8,7 @@ import (
 	"os"
 
 	"git.lyda.ie/kevin/bulletin/accounts"
+	"git.lyda.ie/kevin/bulletin/repl"
 
 	"github.com/urfave/cli/v3"
 )
@@ -32,7 +33,10 @@ func main() {
 			if err != nil {
 				return err
 			}
-			fmt.Println("Running as", user)
+			err = repl.Loop(user)
+			if err != nil {
+				return err
+			}
 			return nil
 		},
 	}
diff --git a/repl/command.go b/repl/command.go
new file mode 100644
index 0000000000000000000000000000000000000000..5567900ba7bf065af140e08863794346ad293653
--- /dev/null
+++ b/repl/command.go
@@ -0,0 +1,412 @@
+// Package repl implements the main event loop.
+package repl
+
+type command struct {
+	command   string
+	boolFlags []string
+	strFlags  []string
+	args []string
+	commands  []*command
+	action    func(string, *command) error
+	}
+
+var commands = []*command{
+	{
+		command: "ADD",
+		boolFlags: []string{
+			"/ALL",
+			"/BELL",
+			"/BROADCAST",
+			"/CLUSTER",
+			"/EDIT",
+			"/EXPIRATION",
+			"/EXTRACT",
+			"/FOLDER",
+			"/LOCAL",
+			"/NODES",
+			"/NOINDENT",
+			"/PERMANENT",
+			"/SUBJECT",
+			"/SHUTDOWN",
+			"/SYSTEM",
+			"/USERNAME",
+		},
+	},
+	{
+		command: "ATTACH",
+		// 2 Parameters
+		// 2 Qualifiers
+		// 2 Examples
+	},
+	{
+		command: "BACK",
+	},
+	{
+		command: "BULLETIN",
+	},
+	{
+		command: "CHANGE",
+		boolFlags: []string{
+			"/ALL",
+			"/EDIT",
+			"/EXPIRATION",
+			"/GENERAL",
+			"/HEADER",
+			"/NEW",
+			"/NUMBER",
+			"/PERMANENT",
+			"/SHUTDOWN[=nodename]",
+			"/SUBJECT",
+			"/SYSTEM",
+			"/TEXT",
+		},
+	},
+	{
+		command: "COPY",
+		boolFlags: []string{
+			"/ALL",
+			"/MERGE",
+			"/ORIGINAL",
+		},
+	},
+	{
+		command: "CREATE",
+		boolFlags: []string{
+			"/BRIEF",
+			"/DESCRIPTION",
+			"/ID",
+			"/NODE",
+			"/NOTIFY",
+			"/OWNER",
+			"/PRIVATE",
+			"/READNEW",
+			"/REMOTENAME",
+			"/SHOWNEW",
+			"/SEMIPRIVATE",
+			"/SYSTEM",
+		},
+	},
+	{
+		command: "CURRENT",
+		boolFlags: []string{
+			"/EDIT",
+		},
+	},
+	{
+		command: "DELETE",
+		boolFlags: []string{
+			"/ALL",
+			"/IMMEDIATE",
+			"/NODES",
+			"/SUBJECT",
+			"/USERNAME",
+		},
+	},
+	{
+		command: "DIRECTORY",
+		boolFlags: []string{
+			"/DESCRIBE",
+			"/EXPIRATION",
+			"/FOLDERS",
+			"/MARKED",
+			"/NEW",
+			"/REPLY",
+			"/SEARCH",
+			"/SINCE",
+			"/START",
+			"/SUBJECT",
+		},
+	},
+	{
+		command: "EXIT",
+	},
+	{
+		command: "EXTRACT",
+	},
+	{
+		command: "FILE",
+		boolFlags: []string{
+			"/ALL",
+			"/FF",
+			"/HEADER",
+			"/NEW",
+		},
+	},
+	{
+		command: "Folders",
+	},
+	{
+		command: "HELP",
+	},
+	{
+		command: "INDEX",
+		boolFlags: []string{
+			"/MARKED",
+			"/NEW",
+			"/RESTART",
+		},
+	},
+	{
+		command: "KEYPAD",
+	},
+	{
+		command: "LAST",
+	},
+	{
+		command: "MAIL",
+		boolFlags: []string{
+			"/HEADER",
+			"/SUBJECT",
+		},
+	},
+	{
+		command: "MARK",
+	},
+	{
+		command: "MODIFY",
+		boolFlags: []string{
+			"/DESCRIPTION",
+			"/ID",
+			"/NAME",
+			"/OWNER",
+		},
+	},
+	{
+		command: "MOVE",
+		boolFlags: []string{
+			"/ALL",
+			"/MERGE",
+			"/ORIGINAL",
+		},
+	},
+	{
+		command: "NEXT",
+	},
+	{
+		command: "POST",
+		boolFlags: []string{
+			"/CC",
+			"/EDIT",
+			"/EXTRACT",
+			"/NOINDENT",
+			"/SUBJECT",
+		},
+	},
+	{
+		command: "PRINT",
+		boolFlags: []string{
+			"/ALL",
+			"/FORM",
+			"/HEADER",
+			"/NOTIFY",
+			"/QUEUE",
+		},
+	},
+	{
+		command: "READ",
+		boolFlags: []string{
+			"/EDIT",
+			"/MARKED",
+			"/NEW",
+			"/PAGE",
+			"/SINCE",
+		},
+	},
+	{
+		command: "REMOVE",
+	},
+	{
+		command: "REPLY",
+		boolFlags: []string{
+			"/EXTRACT",
+			"/NOINDENT",
+		},
+	},
+	{
+		command: "RESPOND",
+		boolFlags: []string{
+			"/CC",
+			"/EDIT",
+			"/EXTRACT",
+			"/LIST",
+			"/NOINDENT",
+			"/SUBJECT",
+		},
+	},
+	{
+		command: "QUIT",
+	},
+	{
+		command: "SEARCH",
+		boolFlags: []string{
+			"/REPLY",
+			"/REVERSE",
+			"/START",
+			"/SUBJECT",
+		},
+	},
+	{
+		command: "SELECT",
+		boolFlags: []string{
+			"/MARKED",
+		},
+	},
+	{
+		command: "SET",
+		commands: []*command{
+			{
+				command: "ACCESS",
+				boolFlags: []string{
+					//3 id
+					"/ALL",
+					"/READ",
+					//3 Warning
+				},
+			},
+			{
+				command: "BBOARD",
+				boolFlags: []string{
+					"/EXPIRATION",
+					"/SPECIAL",
+					"/VMSMAIL",
+					//3 More_information
+				},
+			},
+			{
+				command: "BRIEF",
+				boolFlags: []string{
+					"/ALL",
+					"/DEFAULT",
+					"/FOLDER",
+					"/PERMANENT",
+				},
+			},
+			{
+				command: "CONTINUOUS_BRIEF",
+			},
+			{
+				command: "DEFAULT_EXPIRE",
+			},
+			{
+				command: "DIGEST",
+			},
+			{
+				command: "DUMP",
+			},
+			{
+				command: "EXPIRE_LIMIT",
+			},
+			{
+				command: "FOLDER",
+				boolFlags: []string{
+					"/MARKED",
+				},
+			},
+			{
+				command: "GENERIC",
+				boolFlags: []string{
+					"/DAYS",
+				},
+			},
+			{
+				command: "KEYPAD",
+			},
+			{
+				command: "LOGIN",
+			},
+			{
+				command: "NODE",
+				boolFlags: []string{
+					"/FOLDER",
+				},
+			},
+			{
+				command: "NOTIFY",
+				boolFlags: []string{
+					"/ALL",
+					"/DEFAULT",
+					"/FOLDER",
+					"/PERMANENT",
+				},
+			},
+			{
+				command: "PAGE",
+			},
+			{
+				command: "PRIVILEGES",
+				boolFlags: []string{
+					"/ID",
+				},
+			},
+			{
+				command: "PROMPT_EXPIRE",
+			},
+			{
+				command: "READNEW",
+				boolFlags: []string{
+					"/ALL",
+					"/DEFAULT",
+					"/FOLDER",
+					"/PERMANENT",
+				},
+			},
+			{
+				command: "SHOWNEW",
+				boolFlags: []string{
+					"/ALL",
+					"/DEFAULT",
+					"/FOLDER",
+					"/PERMANENT",
+				},
+			},
+			{
+				command: "STRIP",
+			},
+			{
+				command: "SYSTEM",
+			},
+		},
+	},
+	{
+		command: "SHOW",
+		commands: []*command{
+			{
+				command: "FLAGS",
+			},
+			{
+				command: "FOLDER",
+				boolFlags: []string{
+					"/FULL",
+				},
+			},
+			{
+				command: "KEYPAD",
+				boolFlags: []string{
+					"/PRINT",
+				},
+			},
+			{
+				command: "NEW",
+			},
+			{
+				command: "PRIVILEGES",
+			},
+			{
+				command: "USER",
+				boolFlags: []string{
+					"/ALL",
+					"/LOGIN",
+				},
+			},
+			{
+				command: "VERSION",
+			},
+			{
+				command: "SPAWN",
+			},
+			{
+				command: "UNDELETE",
+			},
+		},
+	},
+}
diff --git a/repl/repl.go b/repl/repl.go
new file mode 100644
index 0000000000000000000000000000000000000000..ce2ecd2db47cb41cb60c7abbcc7d146c8993a198
--- /dev/null
+++ b/repl/repl.go
@@ -0,0 +1,36 @@
+// Package repl implements the main event loop.
+package repl
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/chzyer/readline"
+)
+
+// Loop is the main event loop.
+func Loop(user string) error {
+	fmt.Printf("TODO: get config for user %s using xdg.", user)
+	rl, err := readline.New("BULLETIN> ")
+	if err != nil {
+		return err
+	}
+	defer rl.Close()
+
+	for {
+		line, err := rl.Readline()
+		if err != nil {
+			return err
+		}
+		words := strings.Field(strings.ReplaceAll(line, "/", " /"))
+		if len(words) {
+			continue
+		}
+		switch strings.ToUpper(words[0]) {
+		case "ADD":
+			commandAdd()
+		}
+	}
+
+	return nil
+}