Skip to content

fix: print getting-started message after npm global install#161

Merged
greynewell merged 3 commits intomainfrom
fix/npm-postinstall-message
Apr 30, 2026
Merged

fix: print getting-started message after npm global install#161
greynewell merged 3 commits intomainfrom
fix/npm-postinstall-message

Conversation

@greynewell
Copy link
Copy Markdown
Contributor

@greynewell greynewell commented Apr 30, 2026

Closes #159.

npm install -g @supermodeltools/cli installs the binary but gives no guidance to the user. This PR adds a postinstall message to npm/install.js that directs users to run supermodel inside their project directory.

Changes

  • Add a getting-started message at the end of npm/install.js postinstall script
  • Add a Go test (cmd/npm_package_test.go) that verifies the message exists

TDD

Failing test committed first — the includes getting-started message subtest fails before the fix, and passes after.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests
    • Added validation tests for installation script behavior and messaging
    • Implemented npm postinstall messaging verification
    • Enhanced terminal detection and authentication fallback scenario coverage

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@greynewell has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 8 minutes and 26 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9ad3c9d9-5ba6-46b5-8855-ebf82112d138

📥 Commits

Reviewing files that changed from the base of the PR and between f5c159e and b5896f6.

📒 Files selected for processing (2)
  • cmd/npm_package_test.go
  • npm/install.js

Walkthrough

Tests are added to validate that both shell and npm installation scripts provide post-install guidance without triggering interactive wizards, and a terminal detection function is refactored for test mockability.

Changes

Cohort / File(s) Summary
Installation Script Validation
cmd/install_script_test.go, cmd/npm_package_test.go
New tests verify that install.sh and npm postinstall scripts contain user-guidance messaging (directing users to run supermodel in their project) and do not invoke setup wizard during installation.
Terminal Detection Refactoring
cmd/root.go, cmd/root_test.go
Introduces openDevTty function variable as a mockable hook for opening /dev/tty, enabling testing of stdinIsTerminal() fallback behavior without relying on actual terminal access.
Login Headless Fallback Test
internal/auth/handler_test.go
Adds test coverage for the scenario where browser-based auth fails and the user manually enters their API key via stdin, validating that the fallback prompt is displayed and credentials are saved correctly.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

📦 Install scripts now whisper "get started here,"
No magic wizards triggered, crystal clear,
Tests stand guard on npm and shell,
With mocked terminals breaking the spell,
Users guided home, project-aware and sound. ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes changes to cmd/install_script_test.go, cmd/root_test.go, cmd/root.go, and internal/auth/handler_test.go that appear unrelated to the npm postinstall message objective described in #159. Remove or clarify the scope of changes in install.sh tests, terminal detection tests, and auth fallback tests. Consider splitting these into separate PRs if they address different issues.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Linked Issues check ❓ Inconclusive The PR adds the postinstall getting-started message to npm/install.js and includes a test verifying its presence, directly addressing the requirement in issue #159. However, the changes also include unrelated test files for install.sh, terminal detection, and auth behavior. Clarify whether the install.sh, terminal detection, and auth tests are required by #159 or if they should be split into separate PRs focused on their respective linked issues.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly addresses the main change: adding a getting-started message after npm global install, which aligns perfectly with the PR's primary objective from issue #159.
Description check ✅ Passed The description covers the key sections: what changed, why (closes #159), and test plan checklist. It explains the problem and solution clearly, though the test plan items are unchecked.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/npm-postinstall-message

Review rate limit: 0/5 reviews remaining, refill in 8 minutes and 26 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

@greynewell greynewell force-pushed the fix/npm-postinstall-message branch from f5c159e to 0acc295 Compare April 30, 2026 14:36
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cmd/install_script_test.go`:
- Around line 9-31: The new TestInstallScript adds assertions that will fail
against the current install.sh because it still invokes the setup subcommand and
lacks the getting-started message; either update install.sh to remove the setup
invocation (so it no longer contains the literal `" setup` or "supermodel setup"
matches checked by TestInstallScript) and add a getting-started prompt that
mentions running 'supermodel' (so the lowercased content satisfies the
hasRunSupermodel OR hasGetStarted checks), or remove/move this test out of
cmd/install_script_test.go and into the PR that actually changes install.sh so
tests and script changes remain paired.

In `@cmd/npm_package_test.go`:
- Around line 27-33: The test currently passes if either "get started" or a "run
'supermodel'" variant exists; update the assertion in the anonymous t.Run (uses
variables lower, hasRunSupermodel, hasGetStarted) to require the explicit
guidance by checking both conditions and that the phrase "project directory"
appears (e.g., require hasRunSupermodel && hasGetStarted &&
strings.Contains(lower, "project directory")), and update the t.Error message
accordingly so the test fails unless the full getting-started instruction
including "run 'supermodel' ... in project directory" is present.

In `@cmd/root.go`:
- Around line 17-23: The openDevTty stub currently always returns an error, so
the stdin fallback never opens /dev/tty; replace the stub implementation of
openDevTty with a real attempt to open "/dev/tty" (using os.Open) and return the
resulting *os.File and error (keeping it replaceable for tests), and ensure
callers like stdinIsTerminal can call openDevTty and handle the returned
file/error; on Windows you may still return an appropriate error if os.Open
fails.

In `@internal/auth/handler_test.go`:
- Around line 300-316: The test fails because it references non-existent
injection hooks openBrowserFunc, stdinReader, and loginOut; add package-level
injectable variables in internal/auth/handler.go (e.g., var openBrowserFunc
func(string) error, var stdinReader io.Reader, var loginOut io.Writer) and
update the Login/loginManual/readSecret code paths to use these variables
instead of direct calls to the browser, os.Stdin or stdout; ensure their default
initializations point to the real implementations (openBrowserFunc =
openBrowser, stdinReader = os.Stdin, loginOut = os.Stdout) so production
behavior is unchanged, allowing the test to override them and restore via
t.Cleanup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 517fc3cc-0006-412f-b247-f87dd1790733

📥 Commits

Reviewing files that changed from the base of the PR and between 037157d and f5c159e.

📒 Files selected for processing (5)
  • cmd/install_script_test.go
  • cmd/npm_package_test.go
  • cmd/root.go
  • cmd/root_test.go
  • internal/auth/handler_test.go

Comment thread cmd/install_script_test.go Outdated
Comment on lines +9 to +31
func TestInstallScript(t *testing.T) {
data, err := os.ReadFile("../install.sh")
if err != nil {
t.Fatalf("could not read install.sh: %v", err)
}
content := string(data)

t.Run("does not run setup wizard at install time", func(t *testing.T) {
// The script uses $BINARY variable, so match the literal subcommand argument
if strings.Contains(content, `" setup`) || strings.Contains(content, "supermodel setup") {
t.Error("install.sh must not run the setup subcommand at install time — the wizard now auto-launches from bare 'supermodel' in a project directory (PR #152)")
}
})

t.Run("includes getting-started message", func(t *testing.T) {
lower := strings.ToLower(content)
hasRunSupermodel := strings.Contains(lower, "run 'supermodel'") || strings.Contains(lower, "run \"supermodel\"")
hasGetStarted := strings.Contains(lower, "get started")
if !hasRunSupermodel && !hasGetStarted {
t.Error("install.sh must include a getting-started message directing users to run 'supermodel' in their project directory")
}
})
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This adds a guaranteed-red test without the paired install.sh change.

Right now this test fails against the current script (install.sh still calls setup at install.sh Line 80 and lacks the required getting-started prompt). If this PR is scoped to npm postinstall, this blocks merge unnecessarily—either include the install.sh behavior change here or move this test to the PR that changes install.sh.

🧰 Tools
🪛 GitHub Actions: Build

[error] 19-19: TestInstallScript failed: "install.sh must not run the setup subcommand at install time — the wizard now auto-launches from bare 'supermodel' in a project directory (PR #152)"


[error] 28-28: TestInstallScript failed: "install.sh must include a getting-started message directing users to run 'supermodel' in their project directory"

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/install_script_test.go` around lines 9 - 31, The new TestInstallScript
adds assertions that will fail against the current install.sh because it still
invokes the setup subcommand and lacks the getting-started message; either
update install.sh to remove the setup invocation (so it no longer contains the
literal `" setup` or "supermodel setup" matches checked by TestInstallScript)
and add a getting-started prompt that mentions running 'supermodel' (so the
lowercased content satisfies the hasRunSupermodel OR hasGetStarted checks), or
remove/move this test out of cmd/install_script_test.go and into the PR that
actually changes install.sh so tests and script changes remain paired.

Comment thread cmd/npm_package_test.go
Comment thread cmd/root.go Outdated
Comment on lines +17 to +23
// openDevTty opens /dev/tty (the process's controlling terminal). It is a
// package-level var so tests can replace it with a mock. On Windows,
// os.Open("/dev/tty") will fail, which is expected — Windows Console API
// (used by term.IsTerminal above) handles Windows Terminal/PowerShell.
var openDevTty = func() (*os.File, error) {
return nil, fmt.Errorf("not implemented")
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

openDevTty is a stub and the fallback path is still non-functional.

At Line 21-23 this always errors, and stdinIsTerminal never consumes this hook, so the /dev/tty fallback behavior is still missing (and the new test fails).

Suggested fix
 // openDevTty opens /dev/tty (the process's controlling terminal). It is a
 // package-level var so tests can replace it with a mock. On Windows,
 // os.Open("/dev/tty") will fail, which is expected — Windows Console API
 // (used by term.IsTerminal above) handles Windows Terminal/PowerShell.
 var openDevTty = func() (*os.File, error) {
-	return nil, fmt.Errorf("not implemented")
+	return os.Open("/dev/tty")
 }

 // stdinIsTerminal reports whether stdin is connected to an interactive
 // terminal. Pulled into a var so tests can stub it.
 var stdinIsTerminal = func() bool {
-	return term.IsTerminal(int(syscall.Stdin)) //nolint:unconvert // syscall.Stdin is uintptr on Windows
+	if term.IsTerminal(int(syscall.Stdin)) { //nolint:unconvert // syscall.Stdin is uintptr on Windows
+		return true
+	}
+	f, err := openDevTty()
+	if err != nil {
+		return false
+	}
+	_ = f.Close()
+	return true
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/root.go` around lines 17 - 23, The openDevTty stub currently always
returns an error, so the stdin fallback never opens /dev/tty; replace the stub
implementation of openDevTty with a real attempt to open "/dev/tty" (using
os.Open) and return the resulting *os.File and error (keeping it replaceable for
tests), and ensure callers like stdinIsTerminal can call openDevTty and handle
the returned file/error; on Windows you may still return an appropriate error if
os.Open fails.

Comment thread internal/auth/handler_test.go Outdated
Comment on lines +300 to +316
orig := openBrowserFunc
openBrowserFunc = func(url string) error {
return fmt.Errorf("no display available")
}
t.Cleanup(func() { openBrowserFunc = orig })

// Provide stdin replacement so loginManual can read the pasted key.
stdinInput := "smsk_live_headless_test\n"
origStdinReader := stdinReader
stdinReader = strings.NewReader(stdinInput)
t.Cleanup(func() { stdinReader = origStdinReader })

// Capture output to verify the auth URL was printed.
var outBuf bytes.Buffer
origOut := loginOut
loginOut = &outBuf
t.Cleanup(func() { loginOut = origOut })
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

This test uses undefined injection hooks and currently breaks compilation.

At Line 300-316, openBrowserFunc, stdinReader, and loginOut don’t exist in the package, so all platforms fail to build.

Please either:

  1. add these injectable vars in internal/auth/handler.go and route Login/loginManual/readSecret through them, or
  2. rewrite this test to use existing public seams only.
🧰 Tools
🪛 GitHub Actions: Build

[error] 300-300: Go test compile error: undefined: openBrowserFunc

🪛 GitHub Actions: Lint

[error] 300-300: golangci-lint: build/test error: undefined: openBrowserFunc

🪛 GitHub Check: golangci-lint

[failure] 316-316:
undefined: loginOut (typecheck)


[failure] 315-315:
undefined: loginOut


[failure] 314-314:
undefined: loginOut


[failure] 310-310:
undefined: stdinReader


[failure] 309-309:
undefined: stdinReader


[failure] 308-308:
undefined: stdinReader


[failure] 304-304:
undefined: openBrowserFunc


[failure] 301-301:
undefined: openBrowserFunc


[failure] 300-300:
undefined: openBrowserFunc

🪛 GitHub Check: test (macos-latest)

[failure] 316-316:
undefined: loginOut


[failure] 315-315:
undefined: loginOut


[failure] 314-314:
undefined: loginOut


[failure] 310-310:
undefined: stdinReader


[failure] 309-309:
undefined: stdinReader


[failure] 308-308:
undefined: stdinReader


[failure] 304-304:
undefined: openBrowserFunc


[failure] 301-301:
undefined: openBrowserFunc


[failure] 300-300:
undefined: openBrowserFunc

🪛 GitHub Check: test (ubuntu-latest)

[failure] 316-316:
undefined: loginOut


[failure] 315-315:
undefined: loginOut


[failure] 314-314:
undefined: loginOut


[failure] 310-310:
undefined: stdinReader


[failure] 309-309:
undefined: stdinReader


[failure] 308-308:
undefined: stdinReader


[failure] 304-304:
undefined: openBrowserFunc


[failure] 301-301:
undefined: openBrowserFunc


[failure] 300-300:
undefined: openBrowserFunc

🪛 GitHub Check: test (windows-latest)

[failure] 316-316:
undefined: loginOut


[failure] 315-315:
undefined: loginOut


[failure] 314-314:
undefined: loginOut


[failure] 310-310:
undefined: stdinReader


[failure] 309-309:
undefined: stdinReader


[failure] 308-308:
undefined: stdinReader


[failure] 304-304:
undefined: openBrowserFunc


[failure] 301-301:
undefined: openBrowserFunc


[failure] 300-300:
undefined: openBrowserFunc

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/auth/handler_test.go` around lines 300 - 316, The test fails because
it references non-existent injection hooks openBrowserFunc, stdinReader, and
loginOut; add package-level injectable variables in internal/auth/handler.go
(e.g., var openBrowserFunc func(string) error, var stdinReader io.Reader, var
loginOut io.Writer) and update the Login/loginManual/readSecret code paths to
use these variables instead of direct calls to the browser, os.Stdin or stdout;
ensure their default initializations point to the real implementations
(openBrowserFunc = openBrowser, stdinReader = os.Stdin, loginOut = os.Stdout) so
production behavior is unchanged, allowing the test to override them and restore
via t.Cleanup.

@greynewell greynewell closed this Apr 30, 2026
@greynewell greynewell force-pushed the fix/npm-postinstall-message branch from 0acc295 to 037157d Compare April 30, 2026 14:39
greynewell and others added 2 commits April 30, 2026 10:39
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes #159.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@greynewell greynewell reopened this Apr 30, 2026
…rted

CodeRabbit feedback: use && instead of || so the test requires both
the 'run supermodel' instruction and the 'get started' phrase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@greynewell greynewell marked this pull request as ready for review April 30, 2026 14:46
@greynewell greynewell merged commit dac5942 into main Apr 30, 2026
6 of 7 checks passed
@greynewell greynewell deleted the fix/npm-postinstall-message branch April 30, 2026 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ux: npm global install gives no first-run guidance — 'supermodel' command is not in a project context

1 participant