- Type: Mach-O 64-bit executable arm64
- Size: ~13.7 MB
- Go Version: 1.25.4
- Build Mode: CGO_ENABLED=1
- Architecture: ARM64 (Apple Silicon)
- OS: Darwin (macOS)
apito [command] [flags]
account- Manage accountsbuild- Build project for docker or zipconfig- Manage CLI configurationcreate- Create a new project or plugininit- Initialize Apito CLI system configurationlogs- Show logs for Apito services and databasesplugin- Manage plugins (create, deploy, update, list, status)restart- Restart Apito servicesself-upgrade- Check for updates and upgrade the CLIstart- Start the Apito engine and consolestatus- Show running status for Apito servicesstop- Stop Apito servicesupdate- Update apito engine, console, or self
apito plugin build [plugin-directory] [flags]
-d, --dir string- Plugin directory (alternative to positional argument)-b, --build string- Build method: system or docker (skips interactive prompt)-p, --platform string- Target OS: linux, darwin, or windows (skips interactive prompt)--arch string- Target architecture: amd64 or arm64 (skips interactive prompt)-t, --type string- Go build type: debug, develop, or production (skips interactive prompt)
// plugin_build.go
pluginBuildCmd.Run = func(cmd *cobra.Command, args []string) {
pluginDir, _ := cmd.Flags().GetString("dir")
if pluginDir == "" && len(args) > 0 {
pluginDir = args[0]
}
if pluginDir == "" {
pluginDir = "."
}
buildPlugin(pluginDir, cmd)
}The build command follows the same pattern as --account flag: flags are checked first, then fall back to interactive prompts if not provided.
func determineBuildMethod(cmd *cobra.Command, languageName string, systemAvailable bool) BuildMethod {
// Check for --build flag first
buildFlag, _ := cmd.Flags().GetString("build")
if buildFlag != "" {
method, err := validateBuildMethod(buildFlag)
if err != nil {
print_error(err.Error())
print_warning("Falling back to interactive selection")
} else {
// Validate that system build is available if requested
if method == BuildMethodSystem && !systemAvailable {
print_warning("Runtime not found, falling back to Docker")
return BuildMethodDocker
}
return method
}
}
// If no flag or validation failed, use interactive prompt
// ... interactive selection logic
}func selectTargetPlatform(cmd *cobra.Command) PlatformTarget {
// Check for --platform and --arch flags first
platformFlag, _ := cmd.Flags().GetString("platform")
archFlag, _ := cmd.Flags().GetString("arch")
if platformFlag != "" {
platform, err := validatePlatformArch(platformFlag, archFlag)
if err != nil {
print_error(err.Error())
print_warning("Falling back to interactive selection")
} else {
return platform
}
}
// If no flags or validation failed, use interactive prompt
// ... interactive selection logic
}func determineGoBuildType(cmd *cobra.Command, systemAvailable bool) GoBuildType {
// Check for --type flag first
typeFlag, _ := cmd.Flags().GetString("type")
if typeFlag != "" {
buildType, err := validateGoBuildType(typeFlag)
if err != nil {
print_error(err.Error())
print_warning("Falling back to interactive selection")
} else {
return buildType
}
}
// If no flag or validation failed, use interactive prompt
// ... interactive selection logic
}func validateBuildMethod(value string) (BuildMethod, error) {
normalized := strings.ToLower(strings.TrimSpace(value))
switch normalized {
case "system":
return BuildMethodSystem, nil
case "docker":
return BuildMethodDocker, nil
default:
return "", fmt.Errorf("invalid build method: %s (must be 'system' or 'docker')", value)
}
}
func validatePlatformArch(os, arch string) (PlatformTarget, error) {
// Validates platform/arch combination
// Searches supportedPlatforms for match
// Falls back to constructing PlatformTarget if valid combination
}
func validateGoBuildType(value string) (GoBuildType, error) {
normalized := strings.ToLower(strings.TrimSpace(value))
switch normalized {
case "debug":
return GoBuildDebug, nil
case "develop", "development":
return GoBuildDevelop, nil
case "production":
return GoBuildProduction, nil
default:
return "", fmt.Errorf("invalid build type: %s (must be 'debug', 'develop', or 'production')", value)
}
}apito plugin deploy [plugin-directory] [flags]
-a, --account string- Account to use for deployment-d, --dir string- Plugin directory (alternative to positional argument)
// main.go
rootCmd.AddCommand(pluginCmd)
// plugin_deploy.go
pluginCmd.AddCommand(pluginDeployCmd)Run: func(cmd *cobra.Command, args []string) {
// Get plugin directory
pluginDir, _ := cmd.Flags().GetString("dir")
if pluginDir == "" && len(args) > 0 {
pluginDir = args[0]
}
if pluginDir == "" {
pluginDir = "."
}
// Get account name from flag
accountName, _ := cmd.Flags().GetString("account")
deployPlugin(pluginDir, accountName)
}func deployPlugin(pluginDir, accountName string) {
// Check server configuration
if !checkServerConfig(accountName) {
return
}
// Load plugin configuration
config, err := readPluginConfig(pluginDir)
if err != nil {
print_error("Failed to load plugin configuration: " + err.Error())
return
}
}func checkServerConfig(accountName string) bool {
// Get account configuration
_, err := getAccountConfig(accountName)
if err != nil {
print_error("Account configuration error: " + err.Error())
return false
}
return true
}
func getAccountConfig(accountName string) (AccountConfig, error) {
cfg, err := loadCLIConfig()
if err != nil {
return AccountConfig{}, err
}
// Use provided account name or default
if accountName == "" {
accountName = cfg.DefaultAccount
}
// Interactive account selection if needed
if accountName == "" {
// ... interactive selection logic
}
account, exists := cfg.Accounts[accountName]
if !exists {
return AccountConfig{}, fmt.Errorf("account '%s' does not exist", accountName)
}
// Validate account configuration
if account.ServerURL == "" {
return AccountConfig{}, fmt.Errorf("account '%s' has no server URL configured", accountName)
}
if account.CloudSyncKey == "" {
return AccountConfig{}, fmt.Errorf("account '%s' has no cloud sync key configured", accountName)
}
return account, nil
}func confirmSensitiveOperation(operation, pluginID, accountName string, pluginInfo ...string) bool {
cfg, err := loadCLIConfig()
if err != nil {
print_error("Failed to load configuration: " + err.Error())
return false
}
// Get account info
var account AccountConfig
var actualAccountName string
if accountName != "" {
if acc, exists := cfg.Accounts[accountName]; exists {
account = acc
actualAccountName = accountName
} else {
print_error(fmt.Sprintf("Account '%s' does not exist", accountName))
return false
}
} else {
// Use default account
if cfg.DefaultAccount != "" {
if acc, exists := cfg.Accounts[cfg.DefaultAccount]; exists {
account = acc
actualAccountName = cfg.DefaultAccount
}
}
}
// Display operation details
print_step(fmt.Sprintf("⚠️ Confirmation Required: %s", strings.Title(operation)))
print_status("")
print_status("Operation Details:")
print_status(fmt.Sprintf(" Action: %s", strings.Title(operation)))
print_status(fmt.Sprintf(" Plugin: %s", pluginID))
// Show account info
if actualAccountName != "" {
print_status(fmt.Sprintf(" Account: %s", actualAccountName))
print_status(fmt.Sprintf(" Server: %s", account.ServerURL))
} else {
print_status(" Account: (default - none set)")
}
// Show additional plugin info if provided
if len(pluginInfo) > 0 {
for _, info := range pluginInfo {
print_status(fmt.Sprintf(" %s", info))
}
}
print_status("")
print_warning("This operation cannot be undone!")
// Confirmation prompt
confirmPrompt := promptui.Prompt{
Label: fmt.Sprintf("Are you sure you want to %s plugin '%s'? (y/N)", operation, pluginID),
IsConfirm: true,
Default: "n",
}
if _, err := confirmPrompt.Run(); err != nil {
print_status("Operation cancelled")
return false
}
return true
}// Ask for confirmation before deployment
if !confirmSensitiveOperation("deploy", pluginID, accountName,
fmt.Sprintf("Version: %s", config.Plugin.Version),
fmt.Sprintf("Language: %s", config.Plugin.Language),
fmt.Sprintf("Type: %s", config.Plugin.Type)) {
return
}
// Create deployment package
packagePath, err := createDeploymentPackage(pluginDir, config)
if err != nil {
print_error("Failed to create deployment package: " + err.Error())
return
}
defer os.Remove(packagePath) // Clean up
// Deploy to server
response, err := deployToServer(packagePath, config, false, pluginDir, accountName)
if err != nil {
print_error("Failed to deploy plugin: " + err.Error())
return
}func deployToServer(packagePath string, config *PluginConfig, isUpdate bool, pluginDir, accountName string) (*PluginOperationResponse, error) {
account, err := getAccountConfig(accountName)
if err != nil {
return nil, fmt.Errorf("failed to get account configuration: %w", err)
}
serverURL := account.ServerURL
cloudSyncKey := account.CloudSyncKey
// Create multipart form data
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
// Add plugin package file
file, err := os.Open(packagePath)
if err != nil {
return nil, fmt.Errorf("failed to open package file: %w", err)
}
defer file.Close()
part, err := writer.CreateFormFile("plugin", filepath.Base(packagePath))
if err != nil {
return nil, fmt.Errorf("failed to create form file: %w", err)
}
_, err = io.Copy(part, file)
if err != nil {
return nil, fmt.Errorf("failed to copy file: %w", err)
}
// Add plugin metadata
metadata := map[string]interface{}{
"id": config.Plugin.ID,
"version": config.Plugin.Version,
"language": config.Plugin.Language,
"type": config.Plugin.Type,
"isUpdate": isUpdate,
}
metadataJSON, _ := json.Marshal(metadata)
writer.WriteField("metadata", string(metadataJSON))
writer.Close()
// Create HTTP request
endpoint := "/system/plugin/deploy"
if isUpdate {
endpoint = "/system/plugin/update"
}
req, err := http.NewRequest("POST", serverURL+endpoint, &buf)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
req.Header.Set("X-Apito-Sync-Key", cloudSyncKey)
// Send request
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
// Parse response
var response PluginOperationResponse
if err := json.NewDecoder(resp.Body).Decode(&response); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return &response, nil
}type AccountConfig struct {
ServerURL string `yaml:"server_url"` // Apito server URL for plugin management
CloudSyncKey string `yaml:"cloud_sync_key"` // Cloud sync key for authentication
}
type CLIConfig struct {
Mode string `yaml:"mode"` // "docker" or "manual"
DefaultAccount string `yaml:"default_account"` // Default account name
DefaultPlugin string `yaml:"default_plugin,omitempty"` // Default plugin for operations
Timeout int `yaml:"timeout,omitempty"` // Request timeout in seconds
Accounts map[string]AccountConfig `yaml:"accounts"` // Account configurations
// Legacy fields for backward compatibility
ServerURL string `yaml:"server_url,omitempty"` // Legacy: Apito server URL
CloudSyncKey string `yaml:"cloud_sync_key,omitempty"` // Legacy: Cloud sync key
}- Path:
~/.apito/config.yml - Format: YAML
- Auto-creation: Directory and file created automatically if missing
func loadCLIConfig() (*CLIConfig, error) {
path, err := configFilePath()
if err != nil {
return nil, err
}
cfg := &CLIConfig{
Mode: "docker",
Timeout: 30,
Accounts: make(map[string]AccountConfig),
}
if _, err := os.Stat(path); os.IsNotExist(err) {
return cfg, nil
}
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(data, cfg); err != nil {
return nil, err
}
// Initialize accounts map if nil
if cfg.Accounts == nil {
cfg.Accounts = make(map[string]AccountConfig)
}
// Migration logic for legacy configuration
if len(cfg.Accounts) == 0 && (cfg.ServerURL != "" || cfg.CloudSyncKey != "") {
cfg.Accounts["default"] = AccountConfig{
ServerURL: cfg.ServerURL,
CloudSyncKey: cfg.CloudSyncKey,
}
cfg.DefaultAccount = "default"
}
return cfg, nil
}github.com/spf13/cobra- CLI frameworkgithub.com/spf13/pflag- Flag parsinggithub.com/manifoldco/promptui- Interactive promptsgopkg.in/yaml.v3- YAML configurationgithub.com/joho/godotenv- Environment variables
github.com/apito-io/go-apito-plugin-sdk- Plugin development SDKgithub.com/hashicorp/go-plugin- HashiCorp plugin frameworkgithub.com/hashicorp/go-hclog- Logginggoogle.golang.org/grpc- gRPC communicationgoogle.golang.org/protobuf- Protocol buffers
golang.org/x/sys- System callsgithub.com/cavaliergopher/grab/v3- File downloadsgithub.com/chzyer/readline- Line editinggithub.com/eiannone/keyboard- Keyboard input
-buildmode=exe- Executable build mode-compiler=gc- Go compilerCGO_ENABLED=1- CGO enabledGOARCH=arm64- ARM64 architectureGOOS=darwin- macOS targetGOARM64=v8.0- ARM64 version
- Module: github.com/apito-io/cli
- Version: v0.2.3+dirty
- VCS: Git
- Revision: 2a3b2a7cca47a49441f1b40a78964607cb987fc4
- Build Time: 2025-09-28T19:22:10Z
- Binary Size: ~13.7 MB
- Static Linking: Most dependencies statically linked
- Dynamic Libraries: Only system libraries (libSystem, CoreFoundation, Security)
- Single-threaded: Main execution thread
- HTTP Client: Concurrent HTTP requests for server communication
- File I/O: Synchronous file operations
- User Input: Blocking interactive prompts
- Graceful Degradation: Operations fail gracefully with clear error messages
- User Feedback: Colored output with status indicators
- Logging: Structured logging with different levels (INFO, WARNING, ERROR, SUCCESS)
- Cloud Sync Key: Used for server authentication
- Header:
X-Apito-Sync-Keyfor API requests - Storage: Keys stored in local configuration file
- Transmission: HTTPS for all server communication
- Configuration: Stored in user home directory
- Permissions: 0755 for directories, 0644 for files
- Temporary Files: Automatic cleanup after operations
- HTTPS Only: All server communication over HTTPS
- Timeout: 30-second timeout for HTTP requests
- User Agent: Custom user agent for requests