This file explains how to add new commands to the synkronus-cli project so future LLM agents can extend the CLI without re-analyzing the codebase.
- Entry point:
cmd/synkronus/main.go- Calls
cmd.Execute()frominternal/cmd/root.go.
- Calls
- Command tree (Cobra):
- Root command
synkis defined ininternal/cmd/root.go(rootCmd). - Subcommands live in
internal/cmd/*.goand are registered in each file'sinit()viarootCmd.AddCommand(...)or group commands'AddCommand(...).
- Root command
- HTTP API client:
pkg/client/client.goClientwraps an autogeneratedoapi-codegenclient inpkg/client/generated.- The generated transport is configured with a request editor that adds
Authorization: Bearer <token>viainternal/auth.GetToken(). - Required
x-ode-versionheaders are passed through generated endpoint params using Viperapi.version.
ListUsersreturns typedgenerated.UserListItem(includes optionalpresencefor last-seen);synk user listprints columns for last activity and client count.
- Config handled in
internal/cmd/root.goandinternal/configusing Viper.rootCmddefines persistent flags:--api-url→api.url--api-version→api.version
- Config file:
$HOME/.synkronus.yaml(or--configoverride).
- Authentication flows through
internal/auth:auth.Login()talks to/auth/login, stores tokens in config.auth.GetToken()returns a valid JWT, refreshing if needed.- The CLI assumes the user has run
synk loginbefore calling authenticated endpoints.
When a new CLI command needs to call the Synkronus API, follow this pattern:
-
Extend
pkg/client.Client(preferred)- Add a method on
Clientthat:- Calls the matching generated method from
pkg/client/generated. - Passes
x-ode-versionthrough endpoint params where required. - Handles HTTP status codes and parses or streams the response.
- Calls the matching generated method from
- Add a method on
-
Use the client method in a Cobra command
- Inside
internal/cmd/*.go, create or reuse a command group. - In the command's
RunE, constructc := client.NewClient()and call the new client method.
- Inside
This separation keeps HTTP details in pkg/client and CLI UX in internal/cmd.
Example: the data command group used for data export.
-
Create a new file in
internal/cmd- File name convention:
<group>.go, e.g.data.go,attachments.go,sync.go.
- File name convention:
-
Define the group command
var dataCmd = &cobra.Command{ Use: "data", Short: "Data-related operations", Long: `Commands for working with exported data and statistics.`, }
-
Define one or more subcommands
var dataExportCmd = &cobra.Command{ Use: "export <output_file>", Short: "Export data as a Parquet ZIP archive", Long: `Download a ZIP archive of Parquet exports from the Synkronus API.
Examples: synk data export exports.zip synk data export ./backups/observations_parquet.zip`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { outputFile := args[0] if outputFile == "" { return fmt.Errorf("output_file is required") }
c := client.NewClient()
if err := c.DownloadParquetExport(outputFile); err != nil {
return fmt.Errorf("data export failed: %w", err)
}
fmt.Printf("Parquet export saved to %s\n", outputFile)
return nil
},
}
4. **Register the commands in `init()`**
```go
func init() {
dataCmd.AddCommand(dataExportCmd)
rootCmd.AddCommand(dataCmd)
}
Once this is done, the new hierarchy appears automatically in:
synk --helpsynk data --help- Completion scripts generated by
synk completion [bash|zsh|fish|powershell].
Use existing client methods as templates:
Client.DownloadAppBundleFile(...)for binary downloads.Client.UploadAppBundle(...)for multipart uploads.Client.DownloadParquetExport(...)(added for/dataexport/parquet).
Key steps in Client.DownloadParquetExport(destPath string) error:
-
Call the generated endpoint method:
resp, err := c.api.GetParquetExportZipWithResponse(context.Background())
-
Check
resp.StatusCode()and surface error text if non-200. -
Ensure destination directory exists (
os.MkdirAll). -
Create the output file and
io.Copyfrombytes.NewReader(resp.Body).
Any new file-download-type feature should follow this pattern to keep behavior consistent (auth, error messages, directory creation).
- Positional args: Use
Args: cobra.ExactArgs(n)(orRangeArgs) and read fromargs[index]. - Flags: Use
cmd.Flags().<Type>("name")to read; define ininit()with:Flags().String(...),Bool(...), etc.MarkFlagRequired("name")if needed.
Examples elsewhere in the codebase:
sync.go:sync pull [output_file]with flags:--client-id,--current-version,--schema-types,--limit,--page-token.
attachments.go:attachments upload <file> --id <attachment_id>attachments download <attachment_id> [output_file].
When adding new commands, align with these patterns for a consistent UX.
- Shell completion is implemented once in
internal/cmd/root.goviacompletionCmd. - Any new commands automatically appear in:
- Root and subcommand
--helpoutputs (Cobra handles this). - Generated completion scripts.
- Root and subcommand
LLM agents should not modify completionCmd for normal feature work. Instead, make sure new commands have clear Short and Long descriptions so the autogenerated help is meaningful.
When extending the CLI, LLM agents should:
-
Decide if a new group is needed
- If the feature logically fits an existing group (e.g.,
sync,attachments,data), add a subcommand there. - Otherwise, create a new
<group>.gowith a top-level*cobra.Command.
- If the feature logically fits an existing group (e.g.,
-
Add/extend a client method in
pkg/client- Encapsulate the HTTP call there, using
c.doRequest. - Handle response codes carefully and return Go errors with useful messages.
- Encapsulate the HTTP call there, using
-
Implement the Cobra command
- Parse args/flags.
- Call the client method.
- Print concise, user-friendly output.
-
Register the command in
init()- Attach to the appropriate group and to
rootCmd.
- Attach to the appropriate group and to
-
Update README (optional but recommended)
- Add a short feature bullet and a minimal example in the Usage section.
Following this guide should allow LLM agents to add new commands that are consistent with the existing synkronus-cli design and UX.