Skip to content

feat: add server ssh-info command for non-interactive SSH credentials#245

Merged
leechenghsiu merged 4 commits into
mainfrom
matthewlee/des-633-cli-新增-server-ssh-info-命令輸出非互動式-ssh-連線資訊
May 22, 2026

Hidden character warning

The head ref may contain hidden characters: "matthewlee/des-633-cli-\u65b0\u589e-server-ssh-info-\u547d\u4ee4\u8f38\u51fa\u975e\u4e92\u52d5\u5f0f-ssh-\u9023\u7dda\u8cc7\u8a0a"
Merged

feat: add server ssh-info command for non-interactive SSH credentials#245
leechenghsiu merged 4 commits into
mainfrom
matthewlee/des-633-cli-新增-server-ssh-info-命令輸出非互動式-ssh-連線資訊

Conversation

@leechenghsiu
Copy link
Copy Markdown
Contributor

@leechenghsiu leechenghsiu commented May 21, 2026

Summary

  • Add server ssh-info command that outputs SSH connection info (IP, port, username, password) as JSON
  • Designed for non-interactive use by the skilled agent in sandbox environments
  • For managed servers, retrieves password via RevealServerPassword API; non-managed servers return empty password

Closes DES-633

Test plan

  • --help displays usage correctly
  • Missing --id in non-interactive mode returns error
  • Invalid server ID returns No such server error
  • Managed server outputs full JSON with password
  • Non-managed server outputs JSON with empty password
  • Positional arg works (without --id flag)
  • Output is valid JSON parsable by jq

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 55ca7e45-5fbc-4599-9d10-34c9c38184b0

📥 Commits

Reviewing files that changed from the base of the PR and between 3802fd3 and 8b6b3cd.

📒 Files selected for processing (1)
  • internal/cmd/server/sshinfo/sshinfo.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added a CLI command that outputs JSON with SSH connection details (ip, port, username, password) for dedicated servers.
    • Supports interactive server selection or direct specification via ID/flag; rejects conflicting IDs.
    • Defaults username to "root" when unset; managed servers may reveal a password (or return empty) and errors are reported on failure.

Walkthrough

Adds an ssh-info CLI subcommand that returns JSON SSH connection details for a dedicated server. Supports interactive server selection or direct --id, derives username (defaults to "root"), conditionally reveals managed server passwords, and prints {ip,port,username,password} to stdout.

Changes

SSH Info Command Implementation

Layer / File(s) Summary
Command structure and output schema
internal/cmd/server/sshinfo/sshinfo.go
Defines Options for command configuration, SSHInfo JSON schema (ip, port, username, password), and wires the ssh-info [server-id] Cobra command with a --id flag and positional argument handling.
Interactive and non-interactive execution
internal/cmd/server/sshinfo/sshinfo.go
Adds dispatcher selecting interactive vs non-interactive modes; interactive lists servers with location labels and prompts selection; non-interactive requires --id, fetches server, derives SSH username (defaults to "root"), reveals managed server password if needed, marshals output to JSON, and prints it.
Server command registration
internal/cmd/server/server.go
Imports the sshinfo command package and registers the ssh-info subcommand in the server command group.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly describes the main change: adding a 'server ssh-info' command that outputs SSH credentials for non-interactive use.
Description check ✅ Passed The description is well-related to the changeset, explaining the purpose, functionality, API interactions, and testing that was performed for the new command.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch matthewlee/des-633-cli-新增-server-ssh-info-命令輸出非互動式-ssh-連線資訊

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

Comment thread internal/cmd/server/sshinfo/sshinfo.go Fixed
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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cmd/server/sshinfo/sshinfo.go`:
- Around line 83-85: The error returned when validating opts.id is too specific;
update the check in sshinfo.go (the code that currently tests opts.id == "" in
the function handling the sshinfo command) to return a generic message like
"server id is required" or "server id must be provided (via --id or positional
argument)" instead of "--id is required" so users know they can supply either
--id or the positional server-id; keep the existing validation logic but change
only the error string.
- Around line 100-104: The current code swallows errors from
f.ApiClient.RevealServerPassword for managed servers (server.IsManaged) which
can cause an empty password to be treated as success; change the handling in the
server.IsManaged branch that calls f.ApiClient.RevealServerPassword(ctx,
opts.id) so that if err != nil you return or surface the error (or log and
return a non-nil error) instead of ignoring it, and only assign password = pw
when err == nil and pw != ""; ensure the error from RevealServerPassword is
included in the returned/logged message so failures are visible.
🪄 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: c684a17b-6198-4823-bc70-2aa8a7bee576

📥 Commits

Reviewing files that changed from the base of the PR and between 18268b9 and 569cdbe.

📒 Files selected for processing (2)
  • internal/cmd/server/server.go
  • internal/cmd/server/sshinfo/sshinfo.go

Comment thread internal/cmd/server/sshinfo/sshinfo.go
Comment thread internal/cmd/server/sshinfo/sshinfo.go
@canyugs
Copy link
Copy Markdown
Contributor

canyugs commented May 21, 2026

Panel Review — zeabur/cli PR #245

1. What problem does this PR solve?

The CLI lacks a non-interactive way to retrieve SSH connection credentials for dedicated servers. Agents/automation in sandbox environments need machine-readable access to IP, port, username, and password without spawning an interactive SSH session.

2. How does it solve it?

Adds a server ssh-info [server-id] subcommand that fetches server details via GetServer, optionally reveals the managed-server password via RevealServerPassword, and prints a JSON object (ip, port, username, password) to stdout. Supports both interactive (server picker) and non-interactive (--id flag or positional arg) modes, mirroring the existing server ssh command structure.

3. What are the tradeoffs?

  • Intentionally exposes plaintext credentials on stdout — correct for agent consumption but requires awareness from human operators.
  • Always outputs JSON regardless of --json flag, diverging from CLI-wide f.Printer convention (intentional for this use case, but noted).
  • Silent error swallowing on password fetch means consumers cannot distinguish "non-managed server" from "managed server with failed credential retrieval."

4. Verdict

Consensus: request changes
Reviewers: Aragorn: request changes, Legolas: approve, Gimli: request changes, Boromir: absent, Frodo: approve


🔴 SUGGESTED CHANGES

  • F1. internal/cmd/server/sshinfo/sshinfo.go:101-105 — When RevealServerPassword fails for a managed server, the command exits 0 with "password":"". This is indistinguishable from the non-managed case and will cause agents to attempt authentication with empty credentials instead of surfacing the real failure. Fix: either propagate the error, or add an "is_managed" field to the JSON output so consumers can branch. (raised by: Aragorn, Gimli)

⚖️ Unresolved disagreements

  • Gimli raised F2 (CodeQL "clear-text logging of sensitive information" alert on fmt.Println(password)). This is by-design behavior for the command's purpose, but CodeQL CI is reportedly failing. If CodeQL is a required check, a scoped suppression comment or // nosec annotation may be needed to unblock merge. Other reviewers did not flag this as blocking.

🟡 NIT

  • N1. internal/cmd/server/sshinfo/sshinfo.go:117-120 — Bypasses f.Printer.JSON convention used elsewhere in the CLI (server get, auth status, etc.). Consider using f.Printer.JSON(info) for consistency, or document in the PR description why raw json.Marshal is intentional here. (raised by: Aragorn, Legolas)
  • N2. internal/cmd/server/sshinfo/sshinfo.go — Add a Long: help string warning that the command emits plaintext credentials and should not be piped into shared logs. (raised by: Aragorn)

🟢 INFO

  • I1. Framing fidelity is excellent — title, summary, test plan (7 items), and code are in perfect agreement. The interactive picker is a free bonus following existing UX patterns. (raised by: Frodo)
  • I2. The existing server ssh command has the same silent password-swallow pattern, but it's less problematic there because the SSH connection attempt itself surfaces auth failure interactively. For ssh-info, the silent failure is worse because there's no downstream feedback loop. (raised by: Aragorn)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@canyugs
Copy link
Copy Markdown
Contributor

canyugs commented May 21, 2026

Panel Review Round 2 — zeabur/cli PR #245

Fix commit: 808437f — "fix: address review feedback for server ssh-info"

F1 Resolution: ✅ Fixed

The RevealServerPassword error is now properly propagated:

if err != nil {
    return fmt.Errorf("reveal server password failed: %w", err)
}
password = pw

Managed servers with failed password retrieval now exit non-zero with a clear error message. Agents can distinguish this from the non-managed (empty password) case.

Additional improvement

Error message for missing ID improved: "server ID is required (use --id or positional server-id)" — clearer for users who don't know about the positional arg.

Remaining NITs (non-blocking)

  • N1. Still uses raw json.Marshal + fmt.Println instead of f.Printer.JSON — acceptable given the command's agent-focused purpose.
  • N2. No Long: help string warning about plaintext credentials — minor.

Verdict: approve

All 🔴 findings resolved. Code is clean and correct.

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/cmd/server/sshinfo/sshinfo.go (1)

120-120: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add CodeQL suppression comment for intentional credential output.

CodeQL flagged this line for "clear-text logging of sensitive information". Since outputting the password is by-design for agent consumption (per PR objectives), add a suppression comment to prevent CI failures.

🛡️ Proposed fix with suppression comment
+	// nosec G104 - Intentionally outputting credentials for agent consumption
 	fmt.Println(string(data))
 	return nil

Or use #nosemgrep or similar depending on your security scanner.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cmd/server/sshinfo/sshinfo.go` at line 120, Add a CodeQL suppression
comment directly above the fmt.Println(string(data)) call to mark the clear-text
credential output as intentional for agent consumption; the comment should
reference CodeQL rule (or your scanner alias, e.g., codeql-suppress or
`#nosemgrep`) and include a short justification like "intentional credential
output for agent" so CI will not fail while preserving the existing
fmt.Println(string(data)) behavior.
♻️ Duplicate comments (1)
internal/cmd/server/sshinfo/sshinfo.go (1)

100-106: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate that revealed password is non-empty for managed servers.

The code now correctly returns an error if RevealServerPassword fails (good!), but if the API call succeeds with an empty password (pw == ""), the command will output {"password": ""}, which is indistinguishable from a non-managed server's output. This addresses the F1 blocking concern raised in the PR objectives about indistinguishable failure states.

🔒 Proposed fix to validate empty password
 var password string
 if server.IsManaged {
 	pw, err := f.ApiClient.RevealServerPassword(ctx, opts.id)
 	if err != nil {
 		return fmt.Errorf("reveal server password failed: %w", err)
 	}
+	if pw == "" {
+		return fmt.Errorf("managed server returned empty password")
+	}
 	password = pw
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cmd/server/sshinfo/sshinfo.go` around lines 100 - 106, When
server.IsManaged is true and you call f.ApiClient.RevealServerPassword(ctx,
opts.id) (in sshinfo.go), validate that the returned pw is non-empty; if pw ==
"" return an error (e.g., fmt.Errorf("revealed empty password for managed server
%s", opts.id)) instead of assigning it to password, so the command does not
print an empty password field; update the block that currently assigns password
= pw to perform this check and return on empty result.
🧹 Nitpick comments (3)
internal/cmd/server/sshinfo/sshinfo.go (3)

26-28: ⚡ Quick win

Add Long: field with security warning.

Per reviewer feedback in the PR objectives, the command should include a Long: description warning that this command emits plaintext credentials and should not be piped into shared logs or executed in untrusted environments.

📝 Suggested addition
 cmd := &cobra.Command{
 	Use:   "ssh-info [server-id]",
 	Short: "Get SSH connection info for a dedicated server",
+	Long:  "Get SSH connection info for a dedicated server.\n\nWARNING: This command outputs plaintext credentials including passwords. Do not pipe output to shared logs or execute in untrusted environments.",
 	Args:  cobra.MaximumNArgs(1),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cmd/server/sshinfo/sshinfo.go` around lines 26 - 28, Add a Long
field to the cobra command (the cmd variable of type &cobra.Command) that
contains a clear security warning stating this command emits plaintext
credentials and must not be piped into shared logs, stored in plaintext, or run
in untrusted environments; update the command declaration for ssh-info (Use:
"ssh-info [server-id]") to include Long: with that warning text so it appears in
help output and documents the risk to users.

115-120: 💤 Low value

Consider using f.Printer.JSON for consistency.

Per reviewer feedback in the PR objectives, the implementation bypasses the CLI's f.Printer.JSON convention by using raw json.Marshal. Using the factory's printer would align with CLI conventions, though raw JSON may be intentional for machine-readable output.

🔄 Alternative using factory printer
-	data, err := json.Marshal(info)
-	if err != nil {
-		return fmt.Errorf("marshal failed: %w", err)
-	}
-
-	fmt.Println(string(data))
+	return f.Printer.JSON(info)

If raw json.Marshal is intentional (e.g., to guarantee compact JSON without formatting), consider adding a comment explaining the choice.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cmd/server/sshinfo/sshinfo.go` around lines 115 - 120, Replace the
raw json.Marshal + fmt.Println output with the CLI factory printer by calling
f.Printer.JSON on the SSH info object instead of json.Marshal(info) and
fmt.Println; update the code that currently references json.Marshal and
fmt.Println to use the printer method (e.g., f.Printer.JSON(info)) to follow the
CLI convention used elsewhere, and if compact/raw JSON was intentional leave the
json.Marshal call but add a clear comment above it explaining why the factory
printer is intentionally bypassed.

54-57: ⚡ Quick win

Consider adding a spinner for the API call.

The ListServers API call could take time, and per coding guidelines, CLI commands should use cmdutil.SpinnerCharSet, cmdutil.SpinnerInterval, and cmdutil.SpinnerColor for spinner configuration during potentially long-running operations.

🔄 Example spinner usage
 func runSSHInfoInteractive(f *cmdutil.Factory, opts *Options) error {
+	spinner := f.Prompter.Spinner("Loading servers...", cmdutil.SpinnerCharSet, cmdutil.SpinnerInterval, cmdutil.SpinnerColor)
+	spinner.Start()
 	servers, err := f.ApiClient.ListServers(context.Background())
+	spinner.Stop()
 	if err != nil {
 		return fmt.Errorf("list servers failed: %w", err)
 	}

As per coding guidelines: Use cmdutil.SpinnerCharSet, cmdutil.SpinnerInterval, and cmdutil.SpinnerColor for spinner configuration in CLI commands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cmd/server/sshinfo/sshinfo.go` around lines 54 - 57, Wrap the
potentially slow API call to f.ApiClient.ListServers in a CLI spinner using
cmdutil.NewSpinner (configured with cmdutil.SpinnerCharSet,
cmdutil.SpinnerInterval, and cmdutil.SpinnerColor): start the spinner before
calling ListServers, stop/succeed or stop/fail it after the call depending on
error, and include the same error return (fmt.Errorf("list servers failed: %w",
err)) when stopping on failure; update the block around ListServers to ensure
the spinner is always stopped (defer or explicit stop) and provide a short
success message on completion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@internal/cmd/server/sshinfo/sshinfo.go`:
- Line 120: Add a CodeQL suppression comment directly above the
fmt.Println(string(data)) call to mark the clear-text credential output as
intentional for agent consumption; the comment should reference CodeQL rule (or
your scanner alias, e.g., codeql-suppress or `#nosemgrep`) and include a short
justification like "intentional credential output for agent" so CI will not fail
while preserving the existing fmt.Println(string(data)) behavior.

---

Duplicate comments:
In `@internal/cmd/server/sshinfo/sshinfo.go`:
- Around line 100-106: When server.IsManaged is true and you call
f.ApiClient.RevealServerPassword(ctx, opts.id) (in sshinfo.go), validate that
the returned pw is non-empty; if pw == "" return an error (e.g.,
fmt.Errorf("revealed empty password for managed server %s", opts.id)) instead of
assigning it to password, so the command does not print an empty password field;
update the block that currently assigns password = pw to perform this check and
return on empty result.

---

Nitpick comments:
In `@internal/cmd/server/sshinfo/sshinfo.go`:
- Around line 26-28: Add a Long field to the cobra command (the cmd variable of
type &cobra.Command) that contains a clear security warning stating this command
emits plaintext credentials and must not be piped into shared logs, stored in
plaintext, or run in untrusted environments; update the command declaration for
ssh-info (Use: "ssh-info [server-id]") to include Long: with that warning text
so it appears in help output and documents the risk to users.
- Around line 115-120: Replace the raw json.Marshal + fmt.Println output with
the CLI factory printer by calling f.Printer.JSON on the SSH info object instead
of json.Marshal(info) and fmt.Println; update the code that currently references
json.Marshal and fmt.Println to use the printer method (e.g.,
f.Printer.JSON(info)) to follow the CLI convention used elsewhere, and if
compact/raw JSON was intentional leave the json.Marshal call but add a clear
comment above it explaining why the factory printer is intentionally bypassed.
- Around line 54-57: Wrap the potentially slow API call to
f.ApiClient.ListServers in a CLI spinner using cmdutil.NewSpinner (configured
with cmdutil.SpinnerCharSet, cmdutil.SpinnerInterval, and cmdutil.SpinnerColor):
start the spinner before calling ListServers, stop/succeed or stop/fail it after
the call depending on error, and include the same error return (fmt.Errorf("list
servers failed: %w", err)) when stopping on failure; update the block around
ListServers to ensure the spinner is always stopped (defer or explicit stop) and
provide a short success message on completion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f5cd2bef-7c5c-444f-a3ab-4f158febfc8c

📥 Commits

Reviewing files that changed from the base of the PR and between 569cdbe and 808437f.

📒 Files selected for processing (1)
  • internal/cmd/server/sshinfo/sshinfo.go

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread internal/cmd/server/sshinfo/sshinfo.go Fixed
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cmd/server/sshinfo/sshinfo.go`:
- Around line 120-121: The stdout write error for the credential output is being
ignored at the fmt.Println(string(data)) call; capture and propagate that error
instead of always returning nil so callers see failures (e.g., change the
fmt.Println(string(data)) call to check its error result and return that error).
Keep the intentional gosec nolint comment around the print but ensure the
function returns the write error (replace the current return nil with the
captured error) so broken pipes or stdout failures surface to callers.
🪄 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: 60a85166-6324-47ad-9e7a-75d63507ddd1

📥 Commits

Reviewing files that changed from the base of the PR and between 808437f and 3802fd3.

📒 Files selected for processing (1)
  • internal/cmd/server/sshinfo/sshinfo.go

Comment thread internal/cmd/server/sshinfo/sshinfo.go Outdated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comment thread internal/cmd/server/sshinfo/sshinfo.go Dismissed
@leechenghsiu
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@leechenghsiu leechenghsiu self-assigned this May 21, 2026
Copy link
Copy Markdown
Contributor

@canyugs canyugs left a comment

Choose a reason for hiding this comment

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

LGTM


var password string
if server.IsManaged {
pw, err := f.ApiClient.RevealServerPassword(ctx, opts.id)
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.

[bug] In runSSHInfoNonInteractive, the call pw, err := f.ApiClient.RevealServerPassword(ctx, opts.id) for server.IsManaged only populates password on err == nil && pw != ""; any error (permission, server no longer has an initial password, transient failure, etc.) is discarded and password remains the empty string. The command then emits a success JSON object with no indication that credential retrieval failed. This is acceptable for the ssh sibling (which falls back to interactive password entry) but incorrect for a dedicated non-interactive credential-export command whose primary consumer is automated agents.

Suggestion: Propagate the error (return fmt.Errorf("reveal server password failed: %w", err)) when IsManaged and reveal fails, or extend SSHInfo with an explicit success indicator (e.g. PasswordRevealed bool) and populate it accordingly. At minimum, surface the error via f.Log (when not silenced) before continuing.

Comment thread internal/cmd/server/sshinfo/sshinfo.go Outdated

func runSSHInfoNonInteractive(f *cmdutil.Factory, opts *Options) error {
if opts.id == "" {
return fmt.Errorf("--id is required")
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.

[suggestion] The non-interactive path (reached both when f.Interactive == false and when an ID was supplied even under interactive mode) emits fmt.Errorf("--id is required") when opts.id == "". However, NewCmdSSHInfo documents Use: "ssh-info [server-id]", Args: cobra.MaximumNArgs(1), and the RunE block explicitly accepts a positional argument and copies it into opts.id.

Suggestion: Align the message with the more accurate wording already used in rename/rename.go:89: fmt.Errorf("server-id or --id is required"). (The same imprecise message exists in the sibling ssh/ssh.go:81, get/get.go:78 and reboot/reboot.go:80, but the new command should not perpetuate it.)

serverRenameCmd "github.com/zeabur/cli/internal/cmd/server/rename"
serverRentCmd "github.com/zeabur/cli/internal/cmd/server/rent"
serverSSHCmd "github.com/zeabur/cli/internal/cmd/server/ssh"
serverSSHInfoCmd "github.com/zeabur/cli/internal/cmd/server/sshinfo"
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.

[suggestion] The implementation lives under internal/cmd/server/sshinfo/sshinfo.go (package sshinfo) and is imported as .../sshinfo. All other hyphenated subcommands follow a hyphenated directory that matches their Cobra Use name: port-forward/, search-repo/, auto-recharge/, etc. (with an internal package name that drops the hyphen, e.g. package portforward).

Suggestion: Move the package to internal/cmd/server/ssh-info/sshinfo.go (package clause and file name may stay sshinfo) so the on-disk layout matches the command token "ssh-info" and the precedent set by peer commands. Update the import alias in server.go accordingly.

Password: password,
}

data, err := json.Marshal(info)
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.

[suggestion] runSSHInfoNonInteractive performs data, err := json.Marshal(info); fmt.Println(string(data)) unconditionally, bypassing both the f.JSON flag and f.Printer.JSON (which performs json.MarshalIndent(v, "", " ")). Every other command in the tree that can emit machine-readable output either gates JSON emission on f.JSON or uses the printer. When --json is supplied the root pre-run silences the logger but this command still writes directly; when it is not supplied the emitted JSON is compact rather than the pretty form produced elsewhere.

Suggestion: Either (a) adopt return f.Printer.JSON(info) (accepting pretty-printed output for consistency) or (b) keep the compact direct emission but document that server ssh-info is a special "always-JSON" command and still route through the printer (or a thin helper) so future printer changes are respected.

func NewCmdSSHInfo(f *cmdutil.Factory) *cobra.Command {
opts := &Options{}

cmd := &cobra.Command{
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.

[nit] The &cobra.Command literal only sets Use, Short, Args and RunE. Peer commands with richer help text (e.g. port-forward/port_forward.go) include multi-line examples and a Long description.

Suggestion: Add a short Long or Example field (or at least one-line examples in --help) so zeabur server ssh-info --help and the global help --all output are self-documenting for the non-interactive agent use case.

@leechenghsiu leechenghsiu merged commit 8bd683b into main May 22, 2026
5 checks passed
@leechenghsiu leechenghsiu deleted the matthewlee/des-633-cli-新增-server-ssh-info-命令輸出非互動式-ssh-連線資訊 branch May 22, 2026 05:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants