func updateStatus() {
	if wf.UpdateAvailable() && opts.Query == "" {
		wf.NewItem("Update Available!").
			Subtitle("↩ or ⇥ to install update").
			Valid(false).
			Autocomplete("workflow:update").
			Icon(iconUpdateAvailable)
	}
	if wf.UpdateCheckDue() && !wf.IsRunning(cacheJob) {
		logIfError(runJob(cacheJob, "-housekeeping"), "check for update: %v")
	}
}
Show "update available" message and check for update if due.
package cli
import (
	"crypto/sha256"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"
	aw "github.com/deanishe/awgo"
	"github.com/deanishe/awgo/keychain"
	"github.com/deanishe/awgo/update"
	"github.com/deanishe/awgo/util"
	"github.com/pkg/errors"
	"go.deanishe.net/alfred-booksearch/pkg/gr"
)
var (
	apiKey    = ""
	apiSecret = ""
	version = ""
	navActions []navAction
)
const (
	repo            = "deanishe/alfred-booksearch"
	helpURL         = "https://github.com/deanishe/alfred-booksearch/tree/master/doc"
	issueTrackerURL = "https://github.com/deanishe/alfred-booksearch/issues"
	booksJob   = "booklist"
	cacheJob   = "housekeeping"
	iconsJob   = "icons"
	shelfJob   = "shelf"
	shelvesJob = "shelves"
	feedsJob   = "feeds"
	userJob    = "user"
	seriesJob  = "series"
	bookJob    = "book"
	tokensKey = "oauth_tokens"
	rerunInterval = 0.2
)
var (
	authorsCacheDir string
	booksCacheDir   string
	iconCacheDir    string
	searchCacheDir  string
	seriesCacheDir  string
	shelvesCacheDir string
	scriptsDir     string
	userScriptsDir string
	api   *gr.Client
	store *keychainStore
	wf    *aw.Workflow
)
func init() {
	aw.IconError = iconError
	aw.IconWarning = iconWarning
	wf = aw.New(
		aw.HelpURL(issueTrackerURL),
		update.GitHub(repo),
	)
	authorsCacheDir = filepath.Join(wf.CacheDir(), "authors")
	booksCacheDir = filepath.Join(wf.CacheDir(), "books")
	iconCacheDir = filepath.Join(wf.CacheDir(), "covers")
	searchCacheDir = filepath.Join(wf.CacheDir(), "queries")
	shelvesCacheDir = filepath.Join(wf.CacheDir(), "shelves")
	seriesCacheDir = filepath.Join(wf.CacheDir(), "series")
	scriptsDir = "scripts"
	userScriptsDir = filepath.Join(wf.DataDir(), "scripts")
	navActions = []navAction{
		{"Search", "Search for books", "search", iconBook},
		{"Shelves", "List bookshelves", "shelves", iconShelf},
		{"Configuration", "Workflow configuration", "config", iconConfig},
	}
}
type logger struct{}
func (l logger) Printf(format string, args ...interface{}) {
	log.Output(3, fmt.Sprintf(format, args...))
}
func (l logger) Print(args ...interface{}) {
	log.Output(3, fmt.Sprint(args...))
}
var _ gr.Logger = logger{}
type navAction struct {
	title    string
	subtitle string
	action   string
	icon     *aw.Icon
}
type keychainStore struct {
	name          string
	token, secret string
}
func (s *keychainStore) Save(token, secret string) error {
	kc := keychain.New(s.name)
	if err := kc.Set(tokensKey, token+" "+secret); err != nil {
		return errors.Wrap(err, "save token to Keychain")
	}
	s.token, s.secret = token, secret
	return nil
}
func (s *keychainStore) Load() (token, secret string, err error) {
	return s.token, s.secret, nil
}
var _ gr.TokenStore = (*keychainStore)(nil)
func runHelp() error {
	wf.Configure(aw.TextErrors(true))
	fs.Usage()
	return nil
}
func Run() {
	wf.Run(run)
}
func run() {
	checkErr(opts.Prepare(wf.Args()))
	if opts.FlagNoop {
		return
	}
	if opts.FlagHelp {
		runHelp()
		return
	}
	if opts.FlagOpen {
		_, err := util.RunCmd(exec.Command("/usr/bin/open", opts.Query))
		notifyIfError(err, "open failed", true)
		return
	}
	checkErr(bootstrap())
	if opts.FlagAuthor {
		runAuthor()
		return
	}
	if opts.FlagCacheAuthor {
		runCacheAuthorList()
		return
	}
	if opts.FlagAuthorise {
		runAuthorise()
		return
	}
	if opts.FlagDeauthorise {
		runDeauthorise()
		return
	}
	if opts.FlagUserInfo {
		runUserInfo()
		return
	}
	if opts.FlagHousekeeping {
		runHousekeeping()
		return
	}
	if opts.FlagFeeds {
		runFeeds()
		return
	}
	if opts.FlagConf {
		runConfig()
		return
	}
	if opts.FlagIcons {
		runIcons()
		return
	}
	if opts.FlagShelves {
		runShelves()
		return
	}
	if opts.FlagShelf {
		runShelf()
		return
	}
	if opts.FlagSelectShelves {
		runSelectShelves()
		return
	}
	if opts.FlagCacheShelf {
		runCacheShelf()
		return
	}
	if opts.FlagCacheShelves {
		runCacheShelves()
		return
	}
	if opts.FlagReloadShelf {
		runReloadShelf()
		return
	}
	if opts.FlagReloadShelves {
		runReloadShelves()
		return
	}
	if opts.FlagAddToShelves {
		runAddToShelves()
		return
	}
	if opts.FlagRemoveFromShelf {
		runRemoveFromShelf()
		return
	}
	if opts.FlagSeries {
		runSeries()
		return
	}
	if opts.FlagCacheSeries {
		runCacheSeries()
		return
	}
	if opts.FlagCacheBook {
		runCacheBook()
		return
	}
	if opts.FlagScript {
		runScript()
		return
	}
	if opts.FlagScripts {
		runScripts()
		return
	}
	if opts.FlagExport {
		runExport(opts.FlagJSON)
		return
	}
	if opts.FlagBeep {
		runBeep()
		return
	}
	if opts.FlagSearch {
		runSearch()
		return
	}
	var runVars bool
	fs.Visit(func(f *flag.Flag) {
		if f.Name == "action" || f.Name == "hide" || f.Name == "passvars" || f.Name == "notify" || f.Name == "query" {
			runVars = true
		}
	})
	if runVars {
		runVariables()
		return
	}
}
func bootstrap() error {
	util.MustExist(authorsCacheDir)
	util.MustExist(booksCacheDir)
	util.MustExist(iconCacheDir)
	util.MustExist(searchCacheDir)
	util.MustExist(seriesCacheDir)
	util.MustExist(shelvesCacheDir)
	util.MustExist(userScriptsDir)
	store = &keychainStore{
		name:   wf.BundleID(),
		token:  opts.AccessToken,
		secret: opts.AccessSecret,
	}
	var err error
	if api, err = gr.New(apiKey, apiSecret, store); err != nil {
		return errors.Wrap(err, "create API client")
	}
	api.Log = logger{}
	if !opts.Authorised() {
		return nil
	}
	if opts.UserID == 0 {
		if err := runJob(userJob, "-userinfo"); err != nil {
			return err
		}
	} else if wf.Cache.Exists(shelvesKey) {
		if !wf.IsRunning(feedsJob) {
			var t time.Time
			if wf.Cache.Exists(feedsKey) {
				if err := wf.Cache.LoadJSON(feedsKey, &t); err != nil {
					return err
				}
			}
			if time.Since(t) > opts.MaxCache.Feeds {
				if err := runJob(feedsJob, "-feeds"); err != nil {
					return err
				}
			}
		}
	} else if err := runJob(shelvesJob, "-saveshelves"); err != nil {
		return err
	}
	return nil
}