AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Scaffold Cli Tool

skill-maheshawasare-claude-skills-pro-scaffold-cli-tool · by MaheshAwasare

Scaffold a production-grade CLI — Cobra (Go) or Commander (TS/Node) with config, structured logging, subcommands, shell completion, signed releases via goreleaser/np, and homebrew/scoop tap. Use when starting a new CLI you intend to ship to other people; not for one-off scripts.

No reviews yet
0 installs
34 views
0.0% view→install

Install

$ agentstack add skill-maheshawasare-claude-skills-pro-scaffold-cli-tool

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-maheshawasare-claude-skills-pro-scaffold-cli-tool)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
4mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Scaffold Cli Tool? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Scaffold a CLI Tool

CLIs that get used (vs. abandoned) follow the same shape: subcommand structure, sane config precedence, JSON + human output, completion, signed releases, easy install. This scaffold is that shape.

When to use

  • Building a CLI you'll ship publicly or share across a team.
  • Tool is non-trivial: ≥3 subcommands, takes config from multiple sources.
  • You'll publish via Homebrew, Scoop, npm, or GitHub Releases.

When NOT to use

  • One-off bash script — overkill.
  • Embedded CLI inside a larger app (e.g. next dev) — different scaffolding.
  • TUI-heavy interactive tool — see Bubble Tea / Ink scaffolds (separate skills).

Pick a language

| Choose | When | |---|---| | Go (Cobra) | Single static binary, no runtime to install, cross-compile easy, fastest startup. Default for distribution to non-developers. | | TS/Node (Commander) | You're already in JS/TS shop, npm install is fine for users, you want to share types with backend code. |

Both produce the same UX. The rest of this skill shows both.

Decisions made for you

| Decision | Go | TS/Node | |---|---|---| | CLI lib | spf13/cobra | commander | | Config | spf13/viper (Cobra-friendly) | cosmiconfig + env-paths | | Output | tablewriter + JSON | cli-table3 + JSON | | Logging | slog | pino | | Errors | sentinel + errors.Is | typed Error subclasses | | Tests | stdlib + testify | vitest | | Releases | goreleaser | np + GitHub Actions | | Distribution | Homebrew tap, Scoop bucket, GH Releases | npm, optional Homebrew |

Config precedence (the table users want)

Every CLI must document this. Highest priority wins:

| # | Source | Example | |---|---|---| | 1 | Flag | --api-url=https://x | | 2 | Env var | MYCLI_API_URL=https://x | | 3 | Project config | ./mycli.yaml | | 4 | User config | ~/.config/mycli/config.yaml (XDG) | | 5 | Built-in default | hardcoded |

XDG paths matter: don't dump dotfiles in $HOME. Use os.UserConfigDir() (Go) / env-paths (Node). On Linux: ~/.config/mycli/. Mac: ~/Library/Application Support/mycli/. Windows: %APPDATA%\mycli\.

File structure (Go / Cobra)

mycli/
  cmd/
    mycli/main.go               # entrypoint
  internal/
    cli/
      root.go                   # rootCmd, persistent flags
      version.go                # mycli version
      config.go                 # mycli config get/set
      foo.go                    # mycli foo subcommand group
      foo_list.go               # mycli foo list
      foo_create.go             # mycli foo create
    config/
      config.go                 # struct + loader
    output/
      table.go                  # human format
      json.go                   # JSON format
  .goreleaser.yaml
  Makefile
  README.md
  go.mod
  .github/workflows/release.yml

File structure (TS / Commander)

mycli/
  src/
    bin/mycli.ts                # shebang + dispatch
    cli/
      root.ts
      version.ts
      config.ts
      foo/
        index.ts                # foo subgroup
        list.ts
        create.ts
    config/loader.ts
    output/{table.ts,json.ts}
  package.json                  # "bin": { "mycli": "dist/bin/mycli.js" }
  tsconfig.json
  vitest.config.ts
  .github/workflows/release.yml

Cobra root command

// internal/cli/root.go
var rootCmd = &cobra.Command{
    Use:           "mycli",
    Short:         "Do the thing",
    SilenceUsage:  true,                // don't print usage on every error
    SilenceErrors: true,                // we print our own
    PersistentPreRunE: func(cmd *cobra.Command, _ []string) error {
        return loadConfig(cmd)
    },
}

func init() {
    rootCmd.PersistentFlags().String("config", "", "config file (default: $XDG_CONFIG_HOME/mycli/config.yaml)")
    rootCmd.PersistentFlags().StringP("output", "o", "table", "output format: table|json")
    rootCmd.PersistentFlags().Bool("verbose", false, "verbose logs to stderr")
}

func Execute() {
    if err := rootCmd.Execute(); err != nil {
        fmt.Fprintf(os.Stderr, "Error: %s\n", err)
        os.Exit(1)
    }
}

SilenceUsage: true is non-obvious but critical — without it, every error prints the full --help, which makes piped CLIs unreadable.

Output: human vs JSON (every command, both)

// internal/cli/foo_list.go
var fooListCmd = &cobra.Command{
    Use:   "list",
    Short: "List foos",
    RunE: func(cmd *cobra.Command, _ []string) error {
        foos, err := api.ListFoos(cmd.Context())
        if err != nil {
            return err
        }
        format, _ := cmd.Flags().GetString("output")
        return output.Render(cmd.OutOrStdout(), foos, format)
    },
}

--output=json is non-negotiable. Users will pipe your tool to jq. If you only have a table output, your CLI is dead in scripts.

Stdout vs stderr discipline

| Stream | What goes there | |---|---| | stdout | Data the user is asking for. mycli foo list → list of foos. | | stderr | Logs, progress, errors, warnings, prompts. |

Why: piping mycli foo list | wc -l should count foos, not log lines. Get this wrong and your CLI fails silently inside larger pipelines.

Shell completion

// internal/cli/completion.go
var completionCmd = &cobra.Command{
    Use:   "completion [bash|zsh|fish|powershell]",
    Short: "Generate shell completion",
    Args:  cobra.ExactArgs(1),
    RunE: func(cmd *cobra.Command, args []string) error {
        switch args[0] {
        case "bash":       return rootCmd.GenBashCompletion(os.Stdout)
        case "zsh":        return rootCmd.GenZshCompletion(os.Stdout)
        case "fish":       return rootCmd.GenFishCompletion(os.Stdout, true)
        case "powershell": return rootCmd.GenPowerShellCompletion(os.Stdout)
        }
        return nil
    },
}

README must show how to install completion. Most users won't do it, but the 10% who do become evangelists.

Releases (Go via goreleaser)

.goreleaser.yaml:

project_name: mycli
builds:
  - main: ./cmd/mycli
    env: [CGO_ENABLED=0]
    goos: [linux, darwin, windows]
    goarch: [amd64, arm64]
    ldflags:
      - -s -w -X main.version={{.Version}} -X main.commit={{.ShortCommit}}
archives:
  - format_overrides:
      - goos: windows
        format: zip
brews:
  - tap:
      owner: yourorg
      name: homebrew-tap
    folder: Formula
    homepage: https://github.com/yourorg/mycli
    description: Do the thing
checksum:
  name_template: "checksums.txt"
signs:
  - cmd: cosign
    args: ["sign-blob", "--yes", "--output-signature=${signature}", "${artifact}"]
    artifacts: checksum

GitHub Actions workflow on tag push: goreleaser release --clean. Cosign sigs make supply-chain auditors happy.

Releases (TS via np + GitHub Releases)

package.json:

{
  "bin": { "mycli": "dist/bin/mycli.js" },
  "files": ["dist"],
  "scripts": {
    "build": "tsup src/bin/mycli.ts --format esm --target node20 --clean",
    "release": "np --no-yarn"
  }
}

tsup produces a single bundled JS file with proper shebang. Node 20+ supports --experimental-sea-config if you want true single-binary distribution.

Anti-patterns

  • No --output=json — kills automation use. Add it from the start.
  • Logs on stdout — breaks piping. stderr for everything except output data.
  • Dotfiles in $HOME (~/.mycli) — pollutes home dir. Use XDG / OS conventions.
  • Hardcoded API URLs without env override — every other tool a user has supports MYCLI_API_URL=.... Yours should too.
  • No --version or version that lies — embed git SHA via ldflags. mycli version should print version + commit + build date.
  • Interactive prompts without a --yes/--no-confirm escape hatch — blocks scripts and CI. Always allow non-interactive.
  • Errors via panic — print to stderr, set exit code, leave gracefully. os.Exit(1) ≠ panic.
  • Exit code only 0 or 1 — Unix convention: 2 for misuse, 64–78 for sysexits, custom codes documented. At minimum: 0=ok, 1=error, 2=usage.
  • No completion — users who want completion are the ones who'll evangelize.

Verify it worked

  • [ ] mycli --help shows clean usage; subcommands listed.
  • [ ] mycli foo list -o json | jq '.[0].name' produces a value.
  • [ ] mycli foo list 2>/dev/null returns clean output (logs aren't on stdout).
  • [ ] mycli version prints version, commit SHA, build date.
  • [ ] Setting MYCLI_API_URL=https://x overrides the default; --api-url=https://y overrides the env.
  • [ ] mycli completion bash > /tmp/c && source /tmp/c && mycli shows subcommands.
  • [ ] Config in ~/.config/mycli/config.yaml is read; flags override it.
  • [ ] goreleaser release --snapshot --clean builds binaries for linux/darwin/windows × amd64/arm64.
  • [ ] First Homebrew tap install works: brew tap yourorg/tap && brew install mycli.
  • [ ] Exit code is 0 on success, 1 on runtime error, 2 on bad flags.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.