private static async Task Main()
        {
            Console.CancelKeyPress += (s, e) => { Environment.Exit(-1); };

            // Keeper SDK needs a storage to save configuration
            // such as: last login name, device token, etc
            var configuration = new JsonConfigurationStorage("test.json");


            var prompt = "Enter Email Address: ";

            if (!string.IsNullOrEmpty(configuration.LastLogin))
            {
                Console.WriteLine($"Default Email Address: {configuration.LastLogin}");
            }

            Console.Write(prompt);
            var username = Console.ReadLine();

            if (string.IsNullOrEmpty(username))
            {
                if (string.IsNullOrEmpty(configuration.LastLogin))
                {
                    Console.WriteLine("Bye.");
                    return;
                }

                username = configuration.LastLogin;
            }

            var inReadLine = false;

            var authFlow = new AuthSync(configuration);

            authFlow.UiCallback = new AuthSyncCallback(() =>
            {
                if (!inReadLine)
                {
                    return;
                }
                if (authFlow.Step.State == AuthState.Connected || authFlow.Step.State == AuthState.Error)
                {
                    Console.WriteLine("Press <Enter>");
                }
                else
                {
                    PrintStepHelp(authFlow.Step);
                    PrintStepPrompt(authFlow.Step);
                }
            },
                                                       Console.WriteLine);

            // Login to Keeper
            Console.WriteLine("Logging in...");

            var lastState = authFlow.Step.State;
            await authFlow.Login(username);

            while (!authFlow.IsCompleted)
            {
                if (authFlow.Step.State != lastState)
                {
                    PrintStepHelp(authFlow.Step);
                }

                lastState = authFlow.Step.State;
                PrintStepPrompt(authFlow.Step);
                inReadLine = true;
                var cmd = ReadInput();
                inReadLine = false;
                if (string.IsNullOrEmpty(cmd))
                {
                    continue;
                }

                try
                {
                    await ProcessCommand(authFlow, cmd);
                }
                catch (KeeperAuthFailed)
                {
                    Console.WriteLine("Invalid username or password");
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
            }

            if (authFlow.Step is ErrorStep es)
            {
                Console.WriteLine(es.Message);
            }
            if (!authFlow.IsAuthenticated())
            {
                return;
            }

            var auth  = authFlow;
            var vault = new VaultOnline(auth);

            Console.WriteLine("\nRetrieving records...");
            await vault.SyncDown();

            Console.WriteLine($"Hello {username}!");
            Console.WriteLine($"Vault has {vault.RecordCount} records.");

            // Find record with title "Google"
            var search = vault.Records.FirstOrDefault(x => string.Compare(x.Title, "Google", StringComparison.InvariantCultureIgnoreCase) == 0);

            // Create a record if it does not exist.
            if (search == null)
            {
                search = new PasswordRecord
                {
                    Title    = "Google",
                    Login    = "******",
                    Password = "******",
                    Link     = "https://google.com",
                    Notes    = "Stores google credentials"
                };
                search = await vault.CreateRecord(search);
            }

            var nsd3 = vault.LoadNonSharedData <NonSharedData3>(search.Uid);

            nsd3.Data1 = "1";
            nsd3.Data3 = "3";
            await vault.StoreNonSharedData(search.Uid, nsd3);

            var nsd2 = vault.LoadNonSharedData <NonSharedData2>(search.Uid);

            nsd2.Data2 = "2";
            await vault.StoreNonSharedData(search.Uid, nsd2);

            // Update record.
            search.SetCustomField("Security Token", "11111111");
            search = await vault.UpdateRecord(search);

            // find file attachment.
            var attachment = search.Attachments
                             .FirstOrDefault(x => string.Compare(x.Title, "google", StringComparison.InvariantCultureIgnoreCase) == 0);

            if (attachment == null)
            {
                // Upload local file "google.txt".
                // var uploadTask = new FileAttachmentUploadTask("google.txt")
                var fileContent = Encoding.UTF8.GetBytes("Google");
                using (var stream = new MemoryStream(fileContent))
                {
                    var uploadTask = new AttachmentUploadTask(stream)
                    {
                        Title    = "Google",
                        Name     = "google.txt",
                        MimeType = "text/plain"
                    };
                    await vault.UploadAttachment(search, uploadTask);

                    await vault.UpdateRecord(search, false);
                }
            }
            else
            {
                // Download attachment into the stream
                // The stream could be a local file "google.txt"
                // using (var stream = File.OpenWrite("google.txt"))
                using (var stream = new MemoryStream())
                {
                    await vault.DownloadAttachment(search, attachment.Id, stream);
                }

                // Delete attachment. Remove it from the record
                search.Attachments.Remove(attachment);
                await vault.UpdateRecord(search, false);
            }

            // Find shared folder with name "Google".
            var sharedFolder = vault.SharedFolders
                               .FirstOrDefault(x => string.Compare(x.Name, "Google", StringComparison.InvariantCultureIgnoreCase) == 0);

            if (sharedFolder == null)
            {
                // Create shared folder.
                var folder = await vault.CreateFolder("Google",
                                                      null,
                                                      new SharedFolderOptions
                {
                    ManageRecords = true,
                    ManageUsers   = false,
                    CanEdit       = false,
                    CanShare      = false,
                });

                vault.TryGetSharedFolder(folder.FolderUid, out sharedFolder);
            }

            // Add user to shared folder.
            try
            {
                await vault.PutUserToSharedFolder(sharedFolder.Uid,
                                                  "*****@*****.**",
                                                  UserType.User,
                                                  new SharedFolderUserOptions
                {
                    ManageRecords = false,
                    ManageUsers   = false,
                });
            }
            catch (Exception e)
            {
                Console.WriteLine($"Add user to Shared Folder error: {e.Message}");
            }


            // Add record to shared folder.
            await vault.MoveRecords(new[] { new RecordPath {
                                                RecordUid = search.Uid
                                            } }, sharedFolder.Uid, true);

            if (auth.AuthContext.IsEnterpriseAdmin)
            {
                // Load enterprise data.
                var enterprise = new EnterpriseData(auth);
                await enterprise.GetEnterpriseData();

                // Find team with name "Google".
                var team = enterprise.Teams
                           .FirstOrDefault(x => string.Compare(x.Name, "Google", StringComparison.InvariantCultureIgnoreCase) == 0);
                if (team == null)
                {
                    // Create team.
                    team = await enterprise.CreateTeam(new EnterpriseTeam
                    {
                        Name            = "Google",
                        RestrictEdit    = false,
                        RestrictSharing = true,
                        RestrictView    = false,
                    });
                }

                if (team != null)
                {
                    // Add users to the "Google" team.
                    await enterprise.AddUsersToTeams(
                        new[] { "*****@*****.**", "*****@*****.**" },
                        new[] { team.Uid },
                        Console.WriteLine);
                }
            }

            Console.WriteLine("Press any key to quit");
            Console.ReadKey();
        }
        private static async Task ProcessCommand(AuthSync auth, string command)
        {
            if (command == "?")
            {
                PrintStepHelp(auth.Step);
                return;
            }

            if (auth.Step is DeviceApprovalStep das)
            {
                if (command.StartsWith($"{ChannelCommand}=", StringComparison.InvariantCultureIgnoreCase))
                {
                    var channelText = command.Substring(ChannelCommand.Length + 1).ToLowerInvariant();
                    var channel     = das.Channels.FirstOrDefault(x => x.ChannelText() == channelText);
                    if (channel != default)
                    {
                        das.DefaultChannel = channel;
                    }
                    else
                    {
                        Console.WriteLine($"Device Approval push channel {channelText} not found.");
                    }
                }
                else if (string.Compare(command, PushCommand, StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    await das.SendPush(das.DefaultChannel);
                }
                else
                {
                    await das.SendCode(das.DefaultChannel, command);
                }
            }

            else if (auth.Step is TwoFactorStep tfs)
            {
                if (command.StartsWith($"{ChannelCommand}=", StringComparison.InvariantCultureIgnoreCase))
                {
                    var channelText = command.Substring(ChannelCommand.Length + 1).ToLowerInvariant();
                    var channel     = tfs.Channels.FirstOrDefault(x => x.ChannelText() == channelText);
                    if (channel != default)
                    {
                        tfs.DefaultChannel = channel;
                    }
                    else
                    {
                        Console.WriteLine($"2FA channel {channelText} not found.");
                    }
                }
                else if (command.StartsWith($"{ExpireCommand}=", StringComparison.InvariantCultureIgnoreCase))
                {
                    var expireText = command.Substring(ExpireCommand.Length + 1).ToLowerInvariant();
                    var duration   = Expires.FirstOrDefault(x => x.ExpireText() == expireText);
                    if (duration != default)
                    {
                        tfs.Duration = duration;
                    }
                }
                else
                {
                    var push = tfs.Channels
                               .SelectMany(x => tfs.GetChannelPushActions(x) ?? Enumerable.Empty <TwoFactorPushAction>())
                               .FirstOrDefault(x => x.GetPushActionText() == command);
                    if (push != default)
                    {
                        await tfs.SendPush(push);
                    }
                    else
                    {
                        await tfs.SendCode(tfs.DefaultChannel, command);
                    }
                }
            }
            else if (auth.Step is PasswordStep ps)
            {
                await ps.VerifyPassword(command);
            }
            else if (auth.Step is SsoTokenStep sts)
            {
                if (string.Compare(command, "password", StringComparison.InvariantCultureIgnoreCase) == 0)
                {
                    await sts.LoginWithPassword();
                }
                else
                {
                    await sts.SetSsoToken(command);
                }
            }
            else if (auth.Step is SsoDataKeyStep sdks)
            {
                if (AuthUIExtensions.TryParseDataKeyShareChannel(command, out var channel))
                {
                    await sdks.RequestDataKey(channel);
                }
                else
                {
                    Console.WriteLine($"Invalid data key share channel: {command}");
                }
            }
            else
            {
                Console.WriteLine($"Invalid command. Type \"?\" to list available commands.");
            }
        }