Skip to content

Comments

feat(template): add search, get --raw, and help --all#170

Merged
yuaanlin merged 5 commits intomainfrom
feat/template-get-raw
Feb 18, 2026
Merged

feat(template): add search, get --raw, and help --all#170
yuaanlin merged 5 commits intomainfrom
feat/template-get-raw

Conversation

@yuaanlin
Copy link
Member

@yuaanlin yuaanlin commented Feb 18, 2026

Summary

  • Add template get --raw flag to export raw YAML spec from https://zeabur.com/templates/{code}.yaml, so users can save, modify, and redeploy templates
  • Add template search command to filter templates by keyword (name/description) and sort by deployment count
  • Add help --all flag to print all commands and flags at once, making it easy for AI agents to discover available CLI features
  • Fix template get interactive prompt not being skipped when --code flag is already provided
  • Add CLAUDE.md with project conventions for AI-assisted development

Usage

# Export raw YAML to a file
zeabur template get -c GSQQIJ --raw > my-template.yaml

# Search templates by keyword
zeabur template search wordpress

# Show all commands and flags
zeabur help --all

Test plan

  • zeabur template get -c <code> — shows table as before
  • zeabur template get -c <code> --raw — outputs raw YAML
  • zeabur template search <keyword> — filters and sorts by deployment count
  • zeabur help --all — prints all commands with flags
  • zeabur help — default help still works

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added --raw to view templates as streamed raw YAML.
    • Added template search to find and list templates by keyword.
    • Replaced default help with a custom help command and new --all option to print all commands and flags.
  • Usability

    • Template Code prompt now only appears when no code is provided.
  • Documentation

    • Added developer notes documenting project conventions and workflows.

Allow users to export raw YAML spec from a template so they can
save it locally, modify it, and redeploy with `template deploy`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds raw YAML output to template get, a new template search subcommand, a custom help command replacing Cobra's default, wiring updates in template root, and a new developer notes file (CLAUDE.md).

Changes

Cohort / File(s) Summary
Template Get (raw output)
internal/cmd/template/get/get.go
Adds raw bool to Options, wires --raw flag, adjusts interactive prompt for code, adds getTemplateRaw(code) to GET raw YAML and stream to stdout; imports io, net/http, net/url, os, time.
Template Search command
internal/cmd/template/search/search.go
New search subcommand: Options{keyword string}, NewCmdSearch, and runSearch; prompts in interactive mode, lists templates via API, filters by keyword, sorts by deployment count, prints table.
Template wiring
internal/cmd/template/template.go
Registers new search subcommand via AddCommand.
Help command replacement
internal/cmd/help/help.go, internal/cmd/root/root.go
Adds NewCmdHelp custom help command with --all to enumerate commands/flags; root replaces Cobra's default help with the new command.
Docs / Developer notes
CLAUDE.md
Adds developer notes documenting build/test commands, project structure, subcommand conventions, interactive behavior, and other guidance.
Project metadata
manifest_file, go.mod
Small changes recorded to project manifest and go.mod (dependency updates referenced by new imports).

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant CLI as CLI
    participant HTTP as HTTP Server
    participant Stdout as Stdout

    User->>CLI: run `zeabur template get --code <code> --raw`
    CLI->>CLI: parse flags (opts.raw == true)
    CLI->>HTTP: GET /templates/<code>/raw
    HTTP-->>CLI: 200 + YAML stream
    CLI->>Stdout: stream YAML to stdout
    CLI-->>User: exit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes all three major features introduced: template search, get --raw flag, and help --all flag.

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

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/template-get-raw

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

Copy link

@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.

🧹 Nitpick comments (1)
internal/cmd/template/get/get.go (1)

84-90: Consider handling empty RawSpecYaml when --raw is specified.

If a template exists but RawSpecYaml is empty (e.g., older templates or API edge cases), the user will receive no output, which could be confusing—especially when piping to a file.

💡 Suggested improvement
 	if template == nil || template.Code == "" {
 		fmt.Println("Template not found")
 	} else if opts.raw {
+		if template.RawSpecYaml == "" {
+			return fmt.Errorf("no raw YAML spec available for template %q", opts.code)
+		}
 		fmt.Print(template.RawSpecYaml)
 	} else {
 		f.Printer.Table([]string{"Code", "Name", "Description"}, [][]string{{template.Code, template.Name, template.Description}})
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cmd/template/get/get.go` around lines 84 - 90, When opts.raw is true
but template.RawSpecYaml may be empty, detect that case and provide a clear
fallback instead of printing nothing: inside the existing conditional branch
that checks opts.raw (in the get command handling where template and
template.Code are checked), add a check for template.RawSpecYaml == "" and
either print a message like "Template has no raw spec available" (and exit
nonzero if appropriate) or fall back to displaying the formatted table via
f.Printer.Table; reference the variables template, opts.raw, and
template.RawSpecYaml so the change is applied in the same conditional block that
currently prints template.RawSpecYaml.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/cmd/template/get/get.go`:
- Around line 84-90: When opts.raw is true but template.RawSpecYaml may be
empty, detect that case and provide a clear fallback instead of printing
nothing: inside the existing conditional branch that checks opts.raw (in the get
command handling where template and template.Code are checked), add a check for
template.RawSpecYaml == "" and either print a message like "Template has no raw
spec available" (and exit nonzero if appropriate) or fall back to displaying the
formatted table via f.Printer.Table; reference the variables template, opts.raw,
and template.RawSpecYaml so the change is applied in the same conditional block
that currently prints template.RawSpecYaml.

The Template GraphQL type doesn't have a rawSpecYaml field.
Fetch raw YAML from https://zeabur.com/templates/{code}.yaml instead.
Also skip interactive prompt when --code flag is already provided.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@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 the current code and only fix it if needed.

Inline comments:
In `@internal/cmd/template/get/get.go`:
- Around line 96-109: The getTemplateRaw function currently uses http.Get and
concatenates code into the URL; change it to build the URL with
url.PathEscape(code) to escape slashes and special chars, and use an http.Client
with a sensible timeout (e.g., 10s) instead of http.Get so the call won’t hang
indefinitely; call client.Get on the escaped URL, keep the same response
handling and error returns, and ensure resp.Body is deferred closed as before.

- Add `template search` command to filter templates by keyword
  and sort by deployment count
- Add `help --all` flag to print all commands and flags at once
- Add CLAUDE.md with project conventions for AI-assisted development
- Fix interactive prompt skipping when --code flag is provided

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@yuaanlin yuaanlin changed the title feat(template): add --raw flag to output YAML spec feat(template): add search, get --raw, and help --all Feb 18, 2026
Copy link

@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.

🧹 Nitpick comments (2)
internal/cmd/help/help.go (1)

71-76: Consider moving blank line inside the flags check.

Line 75 always prints a blank line, even when there are no flags displayed. This creates extra spacing between commands that have no local flags.

Proposed fix
 	if len(flags) > 0 {
 		fmt.Println(strings.Join(flags, "\n"))
+		fmt.Println()
 	}
-
-	fmt.Println()
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cmd/help/help.go` around lines 71 - 76, The trailing blank line is
printed unconditionally after printing flags; change the logic so the blank line
is only printed when flags are shown by moving the fmt.Println() into the
existing if len(flags) > 0 { ... } block (i.e., when printing flags via
strings.Join(flags, "\n")). Update the block around the flags variable in
help.go so no extra newline is emitted when len(flags) == 0.
internal/cmd/template/search/search.go (1)

28-36: Keyword from positional arg and --keyword flag may conflict.

When both a positional argument and the --keyword flag are provided, the positional argument (Line 30) overwrites whatever was set by the flag binding (Line 36). This can lead to confusing behavior since cobra processes flags before RunE, meaning args[0] always wins. Consider either:

  1. Removing the flag and using only the positional argument, or
  2. Checking if the flag was explicitly set and erroring on conflict.
Option 1: Use only positional argument (simpler)
 func NewCmdSearch(f *cmdutil.Factory) *cobra.Command {
 	opts := Options{}
 
 	cmd := &cobra.Command{
 		Use:   "search [keyword]",
 		Short: "Search templates by keyword",
 		Args:  cobra.MaximumNArgs(1),
 		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) > 0 {
 				opts.keyword = args[0]
 			}
 			return runSearch(f, opts)
 		},
 	}
 
-	cmd.Flags().StringVarP(&opts.keyword, "keyword", "k", "", "Keyword to search in template name and description")
-
 	return cmd
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cmd/template/search/search.go` around lines 28 - 36, The command
currently lets a positional arg override the bound flag because RunE sets
opts.keyword = args[0] unconditionally; update the command to detect and resolve
conflicts between the positional argument and the flag binding: either remove
the flag binding (cmd.Flags().StringVarP & opts.keyword) and use only args[0] as
the search keyword, or keep the flag and in RunE check whether the flag was
explicitly set via cmd.Flags().Changed("keyword") and if both a flag and a
positional arg are provided return an error (or prefer one behavior
consistently) before calling runSearch(f, opts); modify the RunE handler and/or
the flag registration accordingly to ensure opts.keyword is set
deterministically.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/cmd/help/help.go`:
- Around line 71-76: The trailing blank line is printed unconditionally after
printing flags; change the logic so the blank line is only printed when flags
are shown by moving the fmt.Println() into the existing if len(flags) > 0 { ...
} block (i.e., when printing flags via strings.Join(flags, "\n")). Update the
block around the flags variable in help.go so no extra newline is emitted when
len(flags) == 0.

In `@internal/cmd/template/search/search.go`:
- Around line 28-36: The command currently lets a positional arg override the
bound flag because RunE sets opts.keyword = args[0] unconditionally; update the
command to detect and resolve conflicts between the positional argument and the
flag binding: either remove the flag binding (cmd.Flags().StringVarP &
opts.keyword) and use only args[0] as the search keyword, or keep the flag and
in RunE check whether the flag was explicitly set via
cmd.Flags().Changed("keyword") and if both a flag and a positional arg are
provided return an error (or prefer one behavior consistently) before calling
runSearch(f, opts); modify the RunE handler and/or the flag registration
accordingly to ensure opts.keyword is set deterministically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@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 the current code and only fix it if needed.

Inline comments:
In `@internal/cmd/template/get/get.go`:
- Around line 48-56: After prompting for Template Code when opts.code is empty,
validate the entered value and reject empty input instead of letting it fall
through; call the existing paramCheck (or the same validation used for
non-interactive paths) on opts.code after f.Prompter.Input returns (or loop
re-prompt until non-empty) before calling getTemplate(f, opts) so interactive
and non-interactive flows share the same validation logic (refer to opts.code,
f.Prompter.Input, paramCheck, and getTemplate).

- Validate empty input in interactive mode for template get
- Only print blank line after flags in help --all
- Remove --keyword flag from search, use only positional argument

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@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 the current code and only fix it if needed.

Inline comments:
In `@internal/cmd/template/search/search.go`:
- Around line 39-50: runSearch currently accepts an empty keyword when in
interactive mode because it doesn't validate the value returned by
f.Prompter.Input; after calling f.Prompter.Input in runSearch, check if the
returned keyword (opts.keyword) is empty and return an error (same message used
in non-interactive path, e.g. "keyword is required") instead of
proceeding—update the branch that sets opts.keyword from f.Prompter.Input in
runSearch to validate and return an error when the user presses Enter with no
input.

Comment on lines +39 to +50
func runSearch(f *cmdutil.Factory, opts Options) error {
if opts.keyword == "" {
if f.Interactive {
keyword, err := f.Prompter.Input("Search keyword: ", "")
if err != nil {
return err
}
opts.keyword = keyword
} else {
return fmt.Errorf("keyword is required")
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Validate empty keyword after interactive prompt.

In interactive mode, if the user presses Enter without typing a keyword, the search proceeds with an empty string which matches all templates (since strings.Contains(x, "") is always true). This is inconsistent with non-interactive mode which requires a keyword.

Consider adding validation after the prompt, similar to how template get handles it:

💡 Suggested fix
 func runSearch(f *cmdutil.Factory, opts Options) error {
 	if opts.keyword == "" {
 		if f.Interactive {
 			keyword, err := f.Prompter.Input("Search keyword: ", "")
 			if err != nil {
 				return err
 			}
 			opts.keyword = keyword
+			if opts.keyword == "" {
+				return fmt.Errorf("keyword is required")
+			}
 		} else {
 			return fmt.Errorf("keyword is required")
 		}
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func runSearch(f *cmdutil.Factory, opts Options) error {
if opts.keyword == "" {
if f.Interactive {
keyword, err := f.Prompter.Input("Search keyword: ", "")
if err != nil {
return err
}
opts.keyword = keyword
} else {
return fmt.Errorf("keyword is required")
}
}
func runSearch(f *cmdutil.Factory, opts Options) error {
if opts.keyword == "" {
if f.Interactive {
keyword, err := f.Prompter.Input("Search keyword: ", "")
if err != nil {
return err
}
opts.keyword = keyword
if opts.keyword == "" {
return fmt.Errorf("keyword is required")
}
} else {
return fmt.Errorf("keyword is required")
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/cmd/template/search/search.go` around lines 39 - 50, runSearch
currently accepts an empty keyword when in interactive mode because it doesn't
validate the value returned by f.Prompter.Input; after calling f.Prompter.Input
in runSearch, check if the returned keyword (opts.keyword) is empty and return
an error (same message used in non-interactive path, e.g. "keyword is required")
instead of proceeding—update the branch that sets opts.keyword from
f.Prompter.Input in runSearch to validate and return an error when the user
presses Enter with no input.

@yuaanlin yuaanlin merged commit 7f4686e into main Feb 18, 2026
5 checks passed
@yuaanlin yuaanlin deleted the feat/template-get-raw branch February 18, 2026 05:07
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.

1 participant