Skip to content

Commit 25a843d

Browse files
feat: added ask command
1 parent 29377ac commit 25a843d

8 files changed

Lines changed: 125 additions & 6 deletions

File tree

cmd/ask/agent.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
description: CreateOS CLI assistant — manages VMs, deployments, projects, domains, and more using the createos binary
3+
tools:
4+
bash: true
5+
read: true
6+
---
7+
8+
You are a CreateOS CLI assistant. You help users manage their infrastructure using the `createos` CLI tool.
9+
10+
## Your capabilities
11+
12+
Use the `bash` tool to run `createos` commands on behalf of the user. Always run `createos <command> --help` if you are unsure of the exact flags or syntax before executing.
13+
14+
## Available command groups
15+
16+
- `createos vms` — Manage VM terminal instances (deploy, list, get, resize, ssh, reboot, terminate)
17+
- `createos deployments` — Manage project deployments
18+
- `createos projects` — Manage projects
19+
- `createos domains` — Manage custom domains
20+
- `createos environments` — Manage project environments
21+
- `createos skills` — Manage skills
22+
- `createos users` — Manage user account
23+
- `createos whoami` — Show current authenticated user
24+
25+
## Guidelines
26+
27+
- Before running destructive commands (terminate, delete), confirm with the user.
28+
- If a command requires an ID (e.g. VM ID, project ID), run the relevant `list` command first to find it.
29+
- Keep responses concise — show command output directly rather than rephrasing it.
30+
- If the user is not signed in, tell them to run `createos login`.

cmd/ask/ask.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Package ask provides the AI assistant command powered by OpenCode.
2+
package ask
3+
4+
import (
5+
"context"
6+
_ "embed"
7+
"fmt"
8+
"os"
9+
"os/exec"
10+
"path/filepath"
11+
"strings"
12+
13+
"github.com/urfave/cli/v2"
14+
)
15+
16+
//go:embed agent.md
17+
var agentMarkdown []byte
18+
19+
const agentName = "createos"
20+
21+
// installAgent writes the embedded agent markdown to ~/.opencode/agents/createos.md.
22+
func installAgent() error {
23+
home, err := os.UserHomeDir()
24+
if err != nil {
25+
return fmt.Errorf("could not determine home directory: %w", err)
26+
}
27+
28+
agentsDir := filepath.Join(home, ".opencode", "agents")
29+
if err := os.MkdirAll(agentsDir, 0750); err != nil { //nolint:gosec // user-owned directory
30+
return fmt.Errorf("could not create agents directory: %w", err)
31+
}
32+
33+
agentPath := filepath.Join(agentsDir, agentName+".md")
34+
if err := os.WriteFile(agentPath, agentMarkdown, 0600); err != nil {
35+
return fmt.Errorf("could not write agent file: %w", err)
36+
}
37+
return nil
38+
}
39+
40+
// NewAskCommand returns the ask AI assistant command.
41+
func NewAskCommand() *cli.Command {
42+
return &cli.Command{
43+
Name: "ask",
44+
Usage: "Ask the AI assistant to help manage your infrastructure",
45+
ArgsUsage: "[message]",
46+
Description: "Opens the OpenCode AI assistant pre-configured to use the createos CLI.\n\n" +
47+
" Interactive mode (TUI):\n" +
48+
" createos ask\n\n" +
49+
" Non-interactive mode:\n" +
50+
" createos ask \"list my VMs\"\n" +
51+
" createos ask \"deploy a VM in nyc3\"\n\n" +
52+
" Requires opencode to be installed: https://opencode.ai",
53+
Action: func(c *cli.Context) error {
54+
opencodeBin, err := exec.LookPath("opencode")
55+
if err != nil {
56+
return fmt.Errorf("opencode is not installed or not in PATH\n\n Install it from: https://opencode.ai")
57+
}
58+
59+
if err := installAgent(); err != nil {
60+
return fmt.Errorf("could not install CreateOS agent: %w", err)
61+
}
62+
63+
message := strings.Join(c.Args().Slice(), " ")
64+
65+
var args []string
66+
if message != "" {
67+
args = []string{"run", message, "--agent", agentName}
68+
} else {
69+
args = []string{"--agent", agentName}
70+
}
71+
72+
cmd := exec.CommandContext(context.Background(), opencodeBin, args...) //nolint:gosec
73+
cmd.Stdin = os.Stdin
74+
cmd.Stdout = os.Stdout
75+
cmd.Stderr = os.Stderr
76+
77+
return cmd.Run()
78+
},
79+
}
80+
}

cmd/deployments/deployments.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
// Package deployments provides deployment management commands.
12
package deployments
23

34
import (
45
"github.com/urfave/cli/v2"
56
)
67

8+
// NewDeploymentsCommand returns the deployments command group.
79
func NewDeploymentsCommand() *cli.Command {
810
return &cli.Command{
911
Name: "deployments",

cmd/domains/domains.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
// Package domains provides custom domain management commands.
12
package domains
23

34
import (
45
"github.com/urfave/cli/v2"
56
)
67

8+
// NewDomainsCommand returns the domains command group.
79
func NewDomainsCommand() *cli.Command {
810
return &cli.Command{
911
Name: "domains",

cmd/environments/environments.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
// Package environments provides environment management commands.
12
package environments
23

34
import "github.com/urfave/cli/v2"
45

6+
// NewEnvironmentsCommand returns the environments command group.
57
func NewEnvironmentsCommand() *cli.Command {
68
return &cli.Command{
79
Name: "environments",

cmd/root/root.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/NodeOps-app/createos-cli/cmd/domains"
1414
"github.com/NodeOps-app/createos-cli/cmd/environments"
1515
"github.com/NodeOps-app/createos-cli/cmd/oauth"
16+
"github.com/NodeOps-app/createos-cli/cmd/ask"
1617
"github.com/NodeOps-app/createos-cli/cmd/projects"
1718
"github.com/NodeOps-app/createos-cli/cmd/skills"
1819
"github.com/NodeOps-app/createos-cli/cmd/users"
@@ -48,7 +49,7 @@ func NewApp() *cli.App {
4849
},
4950
Before: func(c *cli.Context) error {
5051
cmd := c.Args().First()
51-
if cmd == "" || cmd == "login" || cmd == "logout" || cmd == "version" || cmd == "completion" {
52+
if cmd == "" || cmd == "login" || cmd == "logout" || cmd == "version" || cmd == "completion" || cmd == "ask" {
5253
return nil
5354
}
5455

@@ -118,6 +119,7 @@ func NewApp() *cli.App {
118119
fmt.Println(" login Authenticate with CreateOS")
119120
}
120121
fmt.Println(" completion Generate shell completion script")
122+
fmt.Println(" ask Ask the AI assistant to help manage your infrastructure")
121123
fmt.Println(" version Print the current version")
122124
fmt.Println()
123125
fmt.Println("Run 'createos <command> --help' for more information on a command.")
@@ -129,6 +131,7 @@ func NewApp() *cli.App {
129131
auth.NewLogoutCommand(),
130132
completion.NewCompletionCommand(),
131133
deployments.NewDeploymentsCommand(),
134+
ask.NewAskCommand(),
132135
domains.NewDomainsCommand(),
133136
environments.NewEnvironmentsCommand(),
134137
oauth.NewOAuthCommand(),

internal/api/vm_terminal.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,23 +103,23 @@ type VMDeployment struct {
103103

104104
// vmResourceInputs is the resource spec for VM creation/resize.
105105
type vmResourceInputs struct {
106-
CPU int64 `json:"cpuInMillicores"`
106+
CPU int64 `json:"cpuInMillicores"`
107107
MemoryMiB int64 `json:"memoryInMB"`
108-
DiskMiB int64 `json:"storageInMB"`
108+
DiskMiB int64 `json:"storageInMB"`
109109
}
110110

111111
// vmCreateInputs is the product-specific inputs for vm-terminal.
112112
type vmCreateInputs struct {
113113
Zone string `json:"ZONE"`
114114
Provider string `json:"PROVIDER"`
115115
SSHKeys []string `json:"SSH_KEYS"`
116-
FirewallRules []VMFirewallRule `json:"FIREWALL_RULES"`
116+
FirewallRules []VMFirewallRule `json:"FIREWALL_RULES"`
117117
}
118118

119119
// vmUpdateInputs is the mutable inputs for PUT /v1/product-deployments/:id.
120120
type vmUpdateInputs struct {
121121
SSHKeys []string `json:"SSH_KEYS"`
122-
FirewallRules []VMFirewallRule `json:"FIREWALL_RULES"`
122+
FirewallRules []VMFirewallRule `json:"FIREWALL_RULES"`
123123
}
124124

125125
// updateVMRequest is the request body for PUT /v1/product-deployments/:id.

internal/terminal/tty.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ import (
1010
// IsInteractive returns true when stdout is a real TTY (i.e. a human is
1111
// watching). Returns false in CI pipelines, scripts, or when output is piped.
1212
func IsInteractive() bool {
13-
return term.IsTerminal(int(os.Stdout.Fd()))
13+
return term.IsTerminal(int(os.Stdout.Fd())) //nolint:gosec // uintptr->int safe on all supported platforms for fd values
1414
}

0 commit comments

Comments
 (0)