-
Notifications
You must be signed in to change notification settings - Fork 2
Add supermodel share command #56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,140 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "fmt" | ||
| "os" | ||
| "time" | ||
|
|
||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/supermodeltools/cli/internal/api" | ||
| "github.com/supermodeltools/cli/internal/audit" | ||
| "github.com/supermodeltools/cli/internal/cache" | ||
| "github.com/supermodeltools/cli/internal/config" | ||
| ) | ||
|
|
||
| func init() { | ||
| var dir string | ||
|
|
||
| c := &cobra.Command{ | ||
| Use: "share", | ||
| Short: "Upload your codebase health report and get a public URL", | ||
| Long: `Runs a health audit and uploads the report to supermodeltools.com, | ||
| returning a short public URL you can share or embed as a README badge. | ||
|
|
||
| Example: | ||
|
|
||
| supermodel share | ||
| supermodel share --dir ./path/to/project`, | ||
| RunE: func(cmd *cobra.Command, _ []string) error { | ||
| return runShare(cmd, dir) | ||
| }, | ||
| SilenceUsage: true, | ||
| } | ||
|
|
||
| c.Flags().StringVar(&dir, "dir", "", "project directory (default: current working directory)") | ||
| rootCmd.AddCommand(c) | ||
| } | ||
|
|
||
| func runShare(cmd *cobra.Command, dir string) error { | ||
| rootDir, projectName, err := resolveAuditDir(dir) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| cfg, err := config.Load() | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| // Run the full audit pipeline. | ||
| ir, err := shareAnalyze(cmd, cfg, rootDir, projectName) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| report := audit.Analyze(ir, projectName) | ||
|
|
||
| impact, err := runImpactForShare(cmd, cfg, rootDir) | ||
| if err != nil { | ||
| fmt.Fprintf(cmd.ErrOrStderr(), "Warning: impact analysis unavailable: %v\n", err) | ||
| } else { | ||
| audit.EnrichWithImpact(report, impact) | ||
| } | ||
|
|
||
| // Render to Markdown. | ||
| var buf bytes.Buffer | ||
| audit.RenderHealth(&buf, report) | ||
|
|
||
| // Upload and get public URL. | ||
| client := api.New(cfg) | ||
| ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) | ||
| defer cancel() | ||
|
|
||
| fmt.Fprintln(cmd.ErrOrStderr(), "Uploading report…") | ||
| url, err := client.Share(ctx, api.ShareRequest{ | ||
| ProjectName: projectName, | ||
| Status: string(report.Status), | ||
| Content: buf.String(), | ||
| }) | ||
| if err != nil { | ||
| return fmt.Errorf("upload failed: %w", err) | ||
| } | ||
|
|
||
| fmt.Fprintf(cmd.OutOrStdout(), "\n Report: %s\n\n", url) | ||
| fmt.Fprintf(cmd.OutOrStdout(), " Add this badge to your README:\n\n") | ||
| fmt.Fprintf(cmd.OutOrStdout(), | ||
| " [](%s)\n\n", | ||
| report.Status, url) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| func shareAnalyze(cmd *cobra.Command, cfg *config.Config, rootDir, projectName string) (*api.SupermodelIR, error) { | ||
| if err := cfg.RequireAPIKey(); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| fmt.Fprintln(cmd.ErrOrStderr(), "Creating repository archive…") | ||
| zipPath, err := audit.CreateZip(rootDir) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("create archive: %w", err) | ||
| } | ||
| defer func() { _ = os.Remove(zipPath) }() | ||
|
|
||
| hash, err := cache.HashFile(zipPath) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("hash archive: %w", err) | ||
| } | ||
|
|
||
| client := api.New(cfg) | ||
| ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) | ||
| defer cancel() | ||
|
|
||
| fmt.Fprintf(cmd.ErrOrStderr(), "Analyzing %s…\n", projectName) | ||
| return client.AnalyzeDomains(ctx, zipPath, "share-"+hash[:16]) | ||
| } | ||
|
|
||
| func runImpactForShare(cmd *cobra.Command, cfg *config.Config, rootDir string) (*api.ImpactResult, error) { | ||
| zipPath, err := audit.CreateZip(rootDir) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer func() { _ = os.Remove(zipPath) }() | ||
|
|
||
| hash, err := cache.HashFile(zipPath) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| client := api.New(cfg) | ||
| ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) | ||
| defer cancel() | ||
|
|
||
| fmt.Fprintln(cmd.ErrOrStderr(), "Running impact analysis…") | ||
| return client.Impact(ctx, zipPath, "share-impact-"+hash[:16], "", "") | ||
| } | ||
|
|
||
| // resolveAuditDir and findGitRoot are defined in cmd/audit.go (same package). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -368,6 +368,22 @@ func (c *Client) DisplayGraph(ctx context.Context, repoID, idempotencyKey string | |
| return &g, nil | ||
| } | ||
|
|
||
| // Share uploads a rendered report and returns a public URL. | ||
| func (c *Client) Share(ctx context.Context, req ShareRequest) (string, error) { | ||
| var resp ShareResponse | ||
| if err := c.request(ctx, "POST", "/v1/share", "application/json", | ||
| jsonBody(req), "", &resp); err != nil { | ||
| return "", err | ||
| } | ||
| return resp.URL, nil | ||
| } | ||
|
|
||
| // jsonBody encodes v as JSON and returns it as an io.Reader. | ||
| func jsonBody(v any) io.Reader { | ||
| b, _ := json.Marshal(v) | ||
| return bytes.NewReader(b) | ||
| } | ||
|
Comment on lines
+381
to
+385
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify ignored json.Marshal errors and current jsonBody usage.
rg -n --type go 'b,\s*_\s*:=\s*json\.Marshal\(' -C2
rg -n --type go '\bjsonBody\s*\(' -C2Repository: supermodeltools/cli Length of output: 920 🏁 Script executed: # Find the request method definition to understand its signature
rg -n --type go 'func\s*\(\w+\s+\*?\w+\)\s+request\s*\(' -A5
# Count all jsonBody usages
rg -n --type go '\bjsonBody\s*\('Repository: supermodeltools/cli Length of output: 642 🏁 Script executed: sed -n '370,378p' internal/api/client.goRepository: supermodeltools/cli Length of output: 379 🏁 Script executed: # Find ShareRequest definition
rg -n --type go 'type\s+ShareRequest\s+struct' -A10
# Check for other similar json.Marshal error ignoring patterns
rg -n --type go 'json\.(Marshal|Unmarshal)\s*\([^)]+\)\s*$' -A1Repository: supermodeltools/cli Length of output: 1997 Don't ignore JSON marshal errors in Right now if encoding fails, you're sending an empty/broken body downstream and hiding the root cause. It's not great practice—even though The suggested fix is incomplete though—changing Complete fix// jsonBody function change
-func jsonBody(v any) io.Reader {
- b, _ := json.Marshal(v)
- return bytes.NewReader(b)
+func jsonBody(v any) (io.Reader, error) {
+ b, err := json.Marshal(v)
+ if err != nil {
+ return nil, err
+ }
+ return bytes.NewReader(b), nil
}
// Call site in Share function change
func (c *Client) Share(ctx context.Context, req ShareRequest) (string, error) {
var resp ShareResponse
+ body, err := jsonBody(req)
+ if err != nil {
+ return "", err
+ }
- if err := c.request(ctx, "POST", "/v1/share", "application/json",
- jsonBody(req), "", &resp); err != nil {
+ if err := c.request(ctx, "POST", "/v1/share", "application/json",
+ body, "", &resp); err != nil {
return "", err
}
return resp.URL, nil🤖 Prompt for AI Agents |
||
|
|
||
| func (c *Client) request(ctx context.Context, method, path, contentType string, body io.Reader, idempotencyKey string, out any) error { | ||
| req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, body) | ||
| if err != nil { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add idempotency support for
POST /v1/shareto prevent duplicate public reports.Right now
Sharehardcodes an empty key, so retries/reruns can create multiple links for the same report.Suggested fix
And in caller (
cmd/share.go), pass a deterministic key (for example from rendered markdown bytes).🤖 Prompt for AI Agents