Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ require (
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mdp/qrterminal/v3 v3.2.1 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
Expand All @@ -59,6 +60,7 @@ require (
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/term v0.30.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
rsc.io/qr v0.2.0 // indirect
)

require (
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,8 @@ github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2J
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4=
github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
Expand Down Expand Up @@ -374,3 +376,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/apimachinery v0.32.1 h1:683ENpaCBjma4CYqsmZyhEzrGz6cjn1MY/X2jB2hkZs=
k8s.io/apimachinery v0.32.1/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
24 changes: 24 additions & 0 deletions internal/errsystem/console.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package errsystem

import (
"bytes"
"encoding/json"
"errors"
"fmt"
Expand All @@ -19,6 +20,7 @@ import (
"github.com/agentuity/go-common/tui"
"github.com/charmbracelet/lipgloss"
"github.com/mattn/go-isatty"
"github.com/mdp/qrterminal/v3"
)

var Version string = "dev"
Expand Down Expand Up @@ -94,6 +96,19 @@ func (e *errSystem) sendReport(filename string) {
}
}

// generateQRCode creates a QR code for the given URL and returns it as a string
func generateQRCode(url string) string {
var buf bytes.Buffer
config := qrterminal.Config{
Level: qrterminal.M,
Writer: &buf,
HalfBlocks: true, // Use half blocks to make QR code more square and compact
QuietZone: 1,
}
qrterminal.GenerateWithConfig(url, config)
return buf.String()
}

// ShowErrorAndExit shows an error message and exits the program.
// If the program is running in a terminal, it will wait for a key press
// and then upload the error report to the Agentuity team.
Expand All @@ -107,6 +122,14 @@ func (e *errSystem) ShowErrorAndExit() {
} else {
body.WriteString(e.code.Message + "\n\n")
}

// Add community help message and QR code when running in terminal
qrCode := generateQRCode(discordURL)
body.WriteString(qrCode)
body.WriteString("\n" + tui.Bold("Get help from the Agentuity community at "))
body.WriteString(tui.Link(discordURL) + " ")
body.WriteString(tui.Muted("(or scan the QR code)") + "\n\n")

Comment on lines +126 to +132
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Gate QR rendering on TTY; avoid dumping block characters into logs/non-interactive output

The comment says “when running in terminal,” but this block runs unconditionally. In non-TTY contexts (CI logs, file redirection), the QR’s block characters reduce readability and bloat logs.

Apply:

-	// Add community help message and QR code when running in terminal
-	qrCode := generateQRCode(discordURL)
-	body.WriteString(qrCode)
-	body.WriteString("\n" + tui.Bold("Get help from the Agentuity community at "))
-	body.WriteString(tui.Link(discordURL) + " ")
-	body.WriteString(tui.Muted("(or scan the QR code)") + "\n\n")
+	// Add community help message and only render QR when running in a TTY
+	if tui.HasTTY && isatty.IsTerminal(os.Stdout.Fd()) {
+		qrCode := generateQRCode(discordURL)
+		body.WriteString(qrCode)
+		body.WriteString("\n" + tui.Bold("Get help from the Agentuity community at "))
+		body.WriteString(tui.Link(discordURL) + " ")
+		body.WriteString(tui.Muted("(or scan the QR code)") + "\n\n")
+	} else {
+		body.WriteString(tui.Bold("Get help from the Agentuity community at "))
+		body.WriteString(tui.Link(discordURL) + "\n\n")
+	}

var detail []string
if e.err != nil {
var apiError *util.APIError
Expand Down Expand Up @@ -138,6 +161,7 @@ func (e *errSystem) ShowErrorAndExit() {
for _, d := range detail {
body.WriteString(tui.Muted(d) + "\n")
}

if !tui.HasTTY {
fmt.Println(body.String())
for k, v := range e.attributes {
Expand Down
70 changes: 70 additions & 0 deletions internal/errsystem/console_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package errsystem

import (
"errors"
"os"
"testing"
)

func TestShowErrorAndExitWithQRCode(t *testing.T) {
// This test demonstrates the QR code functionality
// It will show the error banner with QR code, but we need to prevent actual exit
// We'll use a deferred recover to catch the os.Exit call

Comment on lines +10 to +13
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Recover cannot intercept os.Exit; the comment is incorrect and potentially misleading

os.Exit terminates the process immediately; defer/recover won’t run. This test as written will kill the test process when ShowErrorAndExit executes.

Remove the misleading comment and avoid calling ShowErrorAndExit directly from a normal unit test. See next comment for a safe pattern.

🤖 Prompt for AI Agents
In internal/errsystem/console_test.go around lines 10 to 13, the comment
claiming a deferred recover will catch os.Exit is incorrect and must be removed;
os.Exit terminates the process and defer/recover won’t run. Replace the direct
call to ShowErrorAndExit in the unit test with a safe pattern: either run the
code that calls ShowErrorAndExit in a separate subprocess (using os/exec to run
a helper binary/test that asserts exit code and output) or refactor the package
to inject an exit function (e.g., variable or interface) so tests can replace it
with a no-op or capture call; remove the misleading comment and implement one of
these safe approaches to prevent killing the test process.

// Skip in CI or non-interactive environments
if os.Getenv("CI") != "" {
t.Skip("Skipping interactive test in CI environment")
}

Comment on lines +14 to +18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Skip this “manual” test by default; don’t rely solely on CI detection

Relying only on CI env risks accidental local exits during go test. Gate behind an explicit opt-in env var.

Apply:

-	// Skip in CI or non-interactive environments
-	if os.Getenv("CI") != "" {
-		t.Skip("Skipping interactive test in CI environment")
-	}
+	// Manual/interactive test: require explicit opt-in
+	if os.Getenv("ERRSYSTEM_QR_MANUAL") == "" {
+		t.Skip("Skipping manual ShowErrorAndExit test (set ERRSYSTEM_QR_MANUAL=1 to run)")
+	}

Optionally, replace this test with a subprocess pattern that asserts exit code without killing the parent test runner. I can provide that snippet if you’d like.

🤖 Prompt for AI Agents
In internal/errsystem/console_test.go around lines 14 to 18, the test currently
skips only when CI env is set which can still cause accidental local exits;
change the guard so the test runs only when an explicit opt-in environment
variable is present (e.g., require TEST_INTERACTIVE=true or MANUAL_TEST=1) and
otherwise call t.Skip with a clear message; alternatively (recommended) convert
the test to a subprocess pattern that spawns a child process to exercise the
exit behavior and asserts its exit code without terminating the parent test
runner.

t.Log("This test will show an error with QR code. You should see the Discord QR code in the output.")

// Create a test error
testErr := errors.New("This is a test error to demonstrate the QR code feature")
errSys := New(ErrInvalidConfiguration, testErr,
WithContextMessage("Testing QR code display in unit test"),
WithUserMessage("This test shows the QR code feature working properly"))

// Note: In a real scenario, this would call os.Exit(1)
// For testing purposes, you can comment out the next line to see the QR code
// and manually verify it works, then uncomment it for automated testing

t.Log("Calling ShowErrorAndExit - this will show the QR code and then exit")
t.Log("The QR code should point to: https://discord.gg/agentuity")

// This will actually exit the test, but that's okay for a manual verification test
errSys.ShowErrorAndExit()
}

func TestGenerateQRCode(t *testing.T) {
// Test that QR code generation works
qrCode := generateQRCode("https://discord.gg/agentuity")

// Basic validation that something was generated
if len(qrCode) == 0 {
t.Error("QR code generation returned empty string")
}

// Check that it contains expected QR code characters
if !contains(qrCode, "█") {
t.Error("QR code should contain block characters")
}

t.Logf("Generated QR code length: %d characters", len(qrCode))
}
Comment on lines +38 to +53
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Relax the assertion to support both full- and half-block render modes

generateQRCode currently uses HalfBlocks=true, which typically uses ▀/▄, not █. The test will flake/fail depending on config or library changes.

Apply:

@@
-	// Check that it contains expected QR code characters
-	if !contains(qrCode, "█") {
-		t.Error("QR code should contain block characters")
-	}
+	// Check that it contains expected QR code characters (full or half blocks)
+	if !strings.ContainsAny(qrCode, "█▀▄") {
+		t.Error("QR code should contain block-drawing characters")
+	}

Also update imports to use the stdlib helper (see next comment).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
internal/errsystem/console_test.go lines 38-53: the test assumes full-block
characters (█) but generateQRCode uses HalfBlocks=true so it may produce ▀/▄;
relax the assertion to accept any block char by checking that the output is
non-empty and contains at least one of "█", "▀", or "▄"; replace the custom
contains helper with the standard library strings.Contains and update imports to
include "strings".


func contains(s, substr string) bool {
return len(s) >= len(substr) &&
(s == substr ||
(len(s) > len(substr) && (s[:len(substr)] == substr ||
s[len(s)-len(substr):] == substr ||
containsInner(s, substr))))
}

func containsInner(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
Comment on lines +55 to +70
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove custom contains helpers; they reimplement strings.Contains

These helpers add complexity and potential bugs. Use strings.Contains/ContainsAny instead.

Apply:

-func contains(s, substr string) bool {
-	return len(s) >= len(substr) && 
-		   (s == substr || 
-		    (len(s) > len(substr) && (s[:len(substr)] == substr || 
-		     s[len(s)-len(substr):] == substr || 
-		     containsInner(s, substr))))
-}
-
-func containsInner(s, substr string) bool {
-	for i := 0; i <= len(s)-len(substr); i++ {
-		if s[i:i+len(substr)] == substr {
-			return true
-		}
-	}
-	return false
-}
+// Custom contains helpers removed in favor of strings.Contains/ContainsAny.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In internal/errsystem/console_test.go around lines 55 to 70, the custom contains
and containsInner helpers are reimplementing functionality provided by the
standard library; replace their usage with strings.Contains (or
strings.ContainsAny if matching any rune) and remove these helper functions, and
update imports to include "strings" if not already present; ensure tests call
strings.Contains(s, substr) where appropriate and delete the
contains/containsInner definitions.

Loading