From 644f8e826a883ad2c8032ece44266b5b03bf5af8 Mon Sep 17 00:00:00 2001 From: Ray Ozzie Date: Thu, 29 Oct 2026 13:41:09 -0400 Subject: [PATCH 1/2] update go-m1cpi because of m5 crash; update help agent text --- go.mod | 2 +- go.sum | 4 ++-- lib/go.mod | 2 +- lib/go.sum | 8 ++++---- notehub/help-agent.md | 41 ++++++++++++++--------------------------- 5 files changed, 22 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index 4fe4bf1..8a8327c 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/mattn/go-runewidth v0.0.19 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect - github.com/shoenig/go-m1cpu v0.1.7 // indirect + github.com/shoenig/go-m1cpu v0.2.2 // indirect github.com/tklauser/go-sysconf v0.3.16 // indirect go.bug.st/serial v1.8.0 golang.org/x/sys v0.43.0 // indirect diff --git a/go.sum b/go.sum index ed0cfd7..53cf126 100644 --- a/go.sum +++ b/go.sum @@ -38,8 +38,8 @@ github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6Ng github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= -github.com/shoenig/go-m1cpu v0.1.7 h1:C76Yd0ObKR82W4vhfjZiCp0HxcSZ8Nqd84v+HZ0qyI0= -github.com/shoenig/go-m1cpu v0.1.7/go.mod h1:KkDOw6m3ZJQAPHbrzkZki4hnx+pDRR1Lo+ldA56wD5w= +github.com/shoenig/go-m1cpu v0.2.2 h1:4nc55oVv7nygGnfI9bhLCLzUEs4794y0Bkqx4q2zy7Y= +github.com/shoenig/go-m1cpu v0.2.2/go.mod h1:KkDOw6m3ZJQAPHbrzkZki4hnx+pDRR1Lo+ldA56wD5w= github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk= github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= diff --git a/lib/go.mod b/lib/go.mod index 473bb50..2713e44 100644 --- a/lib/go.mod +++ b/lib/go.mod @@ -12,7 +12,7 @@ require ( github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect - github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/shoenig/go-m1cpu v0.2.2 // indirect github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect diff --git a/lib/go.sum b/lib/go.sum index 0ced6c6..e8aae02 100644 --- a/lib/go.sum +++ b/lib/go.sum @@ -13,10 +13,10 @@ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= +github.com/shoenig/go-m1cpu v0.2.2 h1:4nc55oVv7nygGnfI9bhLCLzUEs4794y0Bkqx4q2zy7Y= +github.com/shoenig/go-m1cpu v0.2.2/go.mod h1:KkDOw6m3ZJQAPHbrzkZki4hnx+pDRR1Lo+ldA56wD5w= +github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk= +github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= diff --git a/notehub/help-agent.md b/notehub/help-agent.md index 1964989..932afcc 100644 --- a/notehub/help-agent.md +++ b/notehub/help-agent.md @@ -16,9 +16,7 @@ credentials and OAuth renewal when available. Your workflow is: lead the user through authentication, fetch the deployed OpenAPI specification, identify the relevant operations and resources, make -requests, and report the results. The default API base is -`https://api.notefile.net`. Use the selected hub consistently for authentication, -discovery, and requests; select another hub only when the user's task calls for it. +requests through the CLI, and report the results. ## 2. Lead the user through authentication @@ -29,7 +27,7 @@ notehub auth status --json ``` Read the single result object's `signed_in` boolean; exit code 0 alone does not -mean signed in. If true, check the reported `hub` and `user`, then continue with +mean signed in. If true, check the reported `user`, then continue with the task. Otherwise inspect `reason` and `action`. A network or temporary service failure calls for retrying the check, not a new login. When sign-in is needed, the suggested `action` is `notehub login`; as an agent, use the `--json` workflow @@ -38,10 +36,12 @@ below. ### Preferred: guide the user through sign-in Tell the user that you will provide a link for them to approve Notehub access. -Use a recognizable name for your agent: +Use the name of the agent harness you are running in, such as `Grok Bot`, `Muse`, +`Claude` or `Codex`, so the user can recognize which agent is requesting access. +Replace `YOUR_HARNESS_NAME` with that actual name: ```sh -notehub login --name "My Agent" --json +notehub login --name "YOUR_HARNESS_NAME" --json ``` This command opens no browser and requires no terminal input. The user opens @@ -53,7 +53,7 @@ the approval link in their own browser, which can be on another machine. 2. **When a message contains `url`, immediately give that exact link to the user.** In your own friendly words, ask them to open it in their browser, sign in, and approve access for your named agent. For example: - “Please open this link in your browser to complete sign-in for My Agent. + “Please open this link in your browser to complete sign-in. I'll keep watching here and continue once it's approved.” The user does not need to copy anything back to you. 3. **Keep monitoring the same process's stdout.** Messages with @@ -106,24 +106,12 @@ an `Authorization` header yourself. ## 3. Discover the entire public API -Fetch the deployed OpenAPI YAML from -[https://api.notefile.net/openapi.yaml](https://api.notefile.net/openapi.yaml): - -```sh -curl -fsSL 'https://api.notefile.net/openapi.yaml' -o notehub-openapi.yaml -``` - -The CLI can fetch the selected hub's specification too: +Fetch the deployed OpenAPI YAML through the CLI: ```sh notehub api --spec > notehub-openapi.yaml ``` -Both fetches are public and need no login. For the default hub they retrieve -the same specification. If the task targets another hub, fetch that hub's spec -with `--spec --hub HUB` and use the same `--hub HUB` for subsequent commands. -The spec must describe the service you are actually calling. - **Use the live YAML as the complete catalog of public API capabilities.** The CLI's named modes are convenient entry points; `notehub api` can call the other operations described in the spec too, without a dedicated CLI command. @@ -155,14 +143,13 @@ Read the full operation, using a YAML parser when helpful: and any permission or resource annotations. Keep using the CLI's login rather than implementing a separate credential exchange from the spec. -Cache the YAML for the task and refresh it when changing hubs or when an -operation appears to have changed. Discover paths and schemas from this file +Cache the YAML for the task. Refresh it when a response contradicts the contract. +Discover paths and schemas from this file instead of inventing endpoint names or relying on deprecated APIs. ## 4. Use the CLI as an authenticated request proxy -For example, an authenticated GET to -`https://api.notefile.net/v1/projects` becomes: +For example, list the user's projects: ```sh notehub api GET /v1/projects @@ -188,8 +175,8 @@ Quote paths containing braces or query strings. | Response status and headers | `--include` | | Save the response body | Shell redirection, such as `> response.json` | -The CLI supplies authentication for each request using the selected hub's saved -login or `NOTEHUB_TOKEN`. You do not need to extract a token, add an authorization +The CLI supplies authentication for each request using the saved login or +`NOTEHUB_TOKEN`. You do not need to extract a token, add an authorization header, or invoke curl for authenticated Notehub operations. ### Identify resources and fill paths @@ -350,4 +337,4 @@ For guidance on how the user might want to interact with their Notehub work, run notehub --agent-chat ``` -It prints the guide from `https://chat.notehub.io`; no login is needed. +It prints Notehub's conversation guide; no login is needed. From b203f1b707f64d2070128bee273671b628e7188c Mon Sep 17 00:00:00 2001 From: Ray Ozzie Date: Fri, 30 Oct 2026 22:20:58 -0400 Subject: [PATCH 2/2] store config in the user config directory instead of ~/note The config (including saved Notehub credentials) now lives in $XDG_CONFIG_HOME/note, or ~/.config/note when that isn't set, and in %AppData%\note on Windows. Container platforms such as muse persist the standard config directory across container replacement, but not an arbitrary ~/note directory, so sign-ins were being lost. ConfigDir now returns an error when no home or config directory can be found, rather than falling back to the current directory, which could leave tokens inside a repository. Co-Authored-By: Claude Opus 5.5 --- lib/config-dir.go | 50 ++++++++++++++++++++++++++++++++++++++ lib/config-refresh_test.go | 20 +++++++++++---- lib/config-storage.go | 11 +++++++-- lib/config-unix.go | 29 ---------------------- lib/config-windows.go | 29 ---------------------- lib/config.go | 5 +++- 6 files changed, 78 insertions(+), 66 deletions(-) create mode 100644 lib/config-dir.go delete mode 100644 lib/config-unix.go delete mode 100644 lib/config-windows.go diff --git a/lib/config-dir.go b/lib/config-dir.go new file mode 100644 index 0000000..f8821bf --- /dev/null +++ b/lib/config-dir.go @@ -0,0 +1,50 @@ +// Copyright 2026 Blues Inc. All rights reserved. +// Use of this source code is governed by licenses granted by the copyright holder. + +package lib + +import ( + "fmt" + "os" + "path/filepath" + "runtime" +) + +// ConfigDir returns the directory holding the configuration, creating it if needed. +// It is "note" within $XDG_CONFIG_HOME, or ~/.config when that isn't set, and within +// %AppData% on Windows. +func ConfigDir() (string, error) { + base, err := configBaseDir() + if err != nil { + return "", fmt.Errorf("can't find the configuration directory: %w", err) + } + path := filepath.Join(base, "note") + if err := os.MkdirAll(path, 0700); err != nil { + return "", fmt.Errorf("can't create the configuration directory: %w", err) + } + return path, nil +} + +func configBaseDir() (string, error) { + if runtime.GOOS == "windows" { + return os.UserConfigDir() + } + // A relative XDG_CONFIG_HOME is invalid and is ignored, as the XDG spec requires + if dir := os.Getenv("XDG_CONFIG_HOME"); filepath.IsAbs(dir) { + return dir, nil + } + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + return filepath.Join(home, ".config"), nil +} + +// Get the pathname of config settings +func defaultConfigSettingsPath() (string, error) { + dir, err := ConfigDir() + if err != nil { + return "", err + } + return filepath.Join(dir, "config.json"), nil +} diff --git a/lib/config-refresh_test.go b/lib/config-refresh_test.go index 15180c4..ae84aad 100644 --- a/lib/config-refresh_test.go +++ b/lib/config-refresh_test.go @@ -30,7 +30,7 @@ func refreshTestConfig(t *testing.T, expires time.Time) *ConfigSettings { oldPath, oldHub := configSettingsPath, configSavedHub t.Cleanup(func() { configSettingsPath, configSavedHub = oldPath, oldHub }) path := filepath.Join(t.TempDir(), "config.json") - configSettingsPath = func() string { return path } + configSettingsPath = func() (string, error) { return path, nil } configSavedHub = "api.other.example" t.Setenv(ConfigTokenEnvVar, "") settings := &ConfigSettings{Hub: "api.example.com", HubCreds: map[string]ConfigCreds{ @@ -78,7 +78,7 @@ func TestCredentialRefresh(t *testing.T) { } } t.Setenv(ConfigTokenEnvVar, test.env) - before, _ := os.ReadFile(configSettingsPath()) + before, _ := os.ReadFile(testConfigPath(t)) calls := 0 original := http.DefaultTransport t.Cleanup(func() { http.DefaultTransport = original }) @@ -105,7 +105,7 @@ func TestCredentialRefresh(t *testing.T) { if test.status == 400 && !errors.Is(err, notehub.ErrRefreshTokenRejected) { t.Fatalf("lost rejection: %v", err) } - after, _ := os.ReadFile(configSettingsPath()) + after, _ := os.ReadFile(testConfigPath(t)) if !changed { if !bytes.Equal(before, after) { t.Error("changed credentials without a successful refresh") @@ -237,7 +237,7 @@ func TestConfigRefreshProcess(t *testing.T) { if path == "" { return } - configSettingsPath = func() string { return path } + configSettingsPath = func() (string, error) { return path, nil } settings, err := readConfigFromFile() if err != nil { t.Fatal(err) @@ -291,7 +291,7 @@ func TestConcurrentProcessRefresh(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) t.Cleanup(cancel) c := &child{cmd: exec.CommandContext(ctx, executable, "-test.run=^TestConfigRefreshProcess$")} - c.cmd.Env = append(os.Environ(), "NOTECLI_REFRESH_TEST_PATH="+configSettingsPath(), "NOTECLI_REFRESH_TEST_URL="+server.URL) + c.cmd.Env = append(os.Environ(), "NOTECLI_REFRESH_TEST_PATH="+testConfigPath(t), "NOTECLI_REFRESH_TEST_URL="+server.URL) c.cmd.Stderr = &c.stderr c.stdin, err = c.cmd.StdinPipe() if err != nil { @@ -324,3 +324,13 @@ func TestConcurrentProcessRefresh(t *testing.T) { t.Fatalf("expected one renewal, got %d", calls.Load()) } } + +// testConfigPath returns the configuration path that the test installed +func testConfigPath(t *testing.T) string { + t.Helper() + path, err := configSettingsPath() + if err != nil { + t.Fatal(err) + } + return path +} diff --git a/lib/config-storage.go b/lib/config-storage.go index 4ae0c6e..07d2219 100644 --- a/lib/config-storage.go +++ b/lib/config-storage.go @@ -20,7 +20,11 @@ var configSettingsPath = defaultConfigSettingsPath func withConfigLock(ctx context.Context, fn func() error) error { ctx, cancel := context.WithTimeout(ctx, 45*time.Second) defer cancel() - file, err := os.OpenFile(configSettingsPath()+".lock", os.O_CREATE|os.O_RDWR, 0600) + path, err := configSettingsPath() + if err != nil { + return err + } + file, err := os.OpenFile(path+".lock", os.O_CREATE|os.O_RDWR, 0600) if err != nil { return fmt.Errorf("locking configuration: %w", err) } @@ -44,7 +48,10 @@ func withConfigLock(ctx context.Context, fn func() error) error { // writeConfigJSON replaces a complete file atomically, so readers never see half // of a rotated token pair. The caller holds the configuration lock. func writeConfigJSON(contents []byte) error { - path := configSettingsPath() + path, err := configSettingsPath() + if err != nil { + return err + } file, err := os.CreateTemp(filepath.Dir(path), ".config-*") if err != nil { return fmt.Errorf("saving configuration: %w", err) diff --git a/lib/config-unix.go b/lib/config-unix.go deleted file mode 100644 index d143911..0000000 --- a/lib/config-unix.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2019 Blues Inc. All rights reserved. -// Use of this source code is governed by licenses granted by the -// copyright holder including that found in the LICENSE file. - -//go:build !windows -// +build !windows - -package lib - -import ( - "os" - "os/user" -) - -// ConfigDir returns the config directory -func ConfigDir() string { - usr, err := user.Current() - if err != nil { - return "." - } - path := usr.HomeDir + "/note" - os.MkdirAll(path, 0777) - return path -} - -// Get the pathname of config settings -func defaultConfigSettingsPath() string { - return ConfigDir() + "/config.json" -} diff --git a/lib/config-windows.go b/lib/config-windows.go deleted file mode 100644 index 39980a1..0000000 --- a/lib/config-windows.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2019 Blues Inc. All rights reserved. -// Use of this source code is governed by licenses granted by the -// copyright holder including that found in the LICENSE file. - -//go:build windows -// +build windows - -package lib - -import ( - "os" - "os/user" -) - -// ConfigDir gets the default directory -func ConfigDir() string { - usr, err := user.Current() - if err != nil { - return "." - } - path := usr.HomeDir + "\\note" - os.MkdirAll(path, 0777) - return path -} - -// Get the pathname of config settings -func defaultConfigSettingsPath() string { - return ConfigDir() + "\\config.json" -} diff --git a/lib/config.go b/lib/config.go index 1ea0464..e71b318 100644 --- a/lib/config.go +++ b/lib/config.go @@ -445,7 +445,10 @@ func defaultConfig() *ConfigSettings { // returns (nil, non-nil) if there was some any other error func readConfigFromFile() (*ConfigSettings, error) { // Read the config file - configPath := configSettingsPath() + configPath, err := configSettingsPath() + if err != nil { + return nil, err + } contents, err := os.ReadFile(configPath) if os.IsNotExist(err) {