Skip to content

Commit 86f389a

Browse files
authored
feat: S3 object-storage command suite + serverless (container/batchjob, pre-release) (#45)
* feat(serverless): add container + batchjob commands `verda serverless container` and `verda serverless batchjob` manage the two serverless deployment shapes on Verda Cloud: always-on HTTPS endpoints and one-shot batch jobs. The web UI's "Deployment type" radio maps to the subcommand choice; each subcommand calls its own SDK service (/container-deployments vs /job-deployments). refactor s3 and add tui for it
1 parent 216f8d8 commit 86f389a

115 files changed

Lines changed: 12541 additions & 272 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.ai/skills/new-command.md

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,20 +112,70 @@ if err != nil {
112112

113113
### 6. Interactive + Non-Interactive
114114

115-
Commands should support both modes:
115+
Commands MUST support both modes:
116116
- **Flags** for scripting/CI (non-interactive)
117-
- **Prompter** for interactive use when flags are missing
117+
- **Prompter** TUI for interactive use when the target is omitted on a terminal
118+
119+
**Implicit trigger.** Omitting the positional target on a TTY launches the
120+
interactive flow; everything else stays one-shot. Gate it exactly like this:
121+
122+
```go
123+
if len(args) >= wantArgs { // explicit args -> run directly
124+
return run(cmd, f, ioStreams, opts, args...)
125+
}
126+
if f.AgentMode() { // agents get a structured error, never a prompt
127+
return cmdutil.NewMissingFlagsError([]string{"s3://bucket/key"})
128+
}
129+
if !interactiveTTY(f) { // non-TTY or json/yaml output -> help, no silent prompt
130+
return cmd.Help()
131+
}
132+
return runInteractive(cmd, f, ioStreams, opts, args)
133+
```
134+
135+
`interactiveTTY(f)` == `IsStdoutTerminal() && !AgentMode() && OutputFormat() == "table"`.
136+
137+
**The hint bar is mandatory on every direct `Select` / `MultiSelect`.** Pass
138+
`tui.WithShowHints(true)` (or `tui.WithMultiSelectShowHints(true)`) so the user
139+
always sees the standard control legend:
140+
141+
```
142+
↑/↓ navigate · type to filter · enter select · esc back · ctrl+c exit
143+
```
144+
145+
(Wizard-engine step Loaders are the only exception — the composite renders its
146+
own hint bar, so double-rendering is a bug.)
147+
148+
**Esc = soft back, Ctrl+C = hard exit — always. Never a confirmation dialog on
149+
either.** Classify the prompter error; never bare-`return nil`:
118150

119151
```go
120-
name := opts.Name
121-
if name == "" {
122-
name, err = prompter.TextInput(ctx, "Item name")
123-
if err != nil {
124-
return nil // User cancelled (Esc/Ctrl+C)
152+
idx, err := f.Prompter().Select(ctx, "Pick one", labels, tui.WithShowHints(true))
153+
if err != nil {
154+
if cmdutil.IsPromptCancel(err) { // Esc OR Ctrl+C — flow doesn't care which
155+
return nil
125156
}
157+
return err // a real I/O failure MUST propagate
126158
}
127159
```
128160

161+
Use `cmdutil.IsPromptInterrupt(err)` (Ctrl+C) and `cmdutil.IsPromptBack(err)`
162+
(Esc) when the two must differ — e.g. a "Back to list / Exit" gate, or a wizard
163+
where Esc steps back one prompt while Ctrl+C exits the whole flow.
164+
165+
**Multi-step wizards.** Model each prompt as its own step walked by an index
166+
into a steps slice: Esc decrements the index (–1 on the first step ends the
167+
flow = exit), Ctrl+C exits, success advances. Print a `Step N of M · Title`
168+
header and a one-time intro banner so the user knows the plan. See
169+
`cmd/s3/move_wizard.go` (`runMoveWizard` + `classifyNav`/`navIdx`) for the
170+
reference pattern.
171+
172+
> **Pitfall that breaks Esc=back:** a shared picker that swallows cancellation
173+
> into `("", nil)` (i.e. `if IsPromptCancel(err) { return "", nil }`) is fine for
174+
> a top-level command, but inside a wizard it destroys back-navigation — the
175+
> wizard can no longer tell Esc (go back) from Ctrl+C (exit) and Esc ends up
176+
> exiting the whole flow. Wizard-facing pickers MUST return the **raw** prompter
177+
> error so the step loop can classify it.
178+
129179
### 7. Output Conventions
130180

131181
- Normal output goes to `ioStreams.Out`
@@ -144,12 +194,22 @@ Delete and dangerous actions MUST confirm:
144194

145195
```go
146196
confirmed, err := prompter.Confirm(ctx, fmt.Sprintf("Delete %s?", name))
147-
if err != nil || !confirmed {
148-
_, _ = fmt.Fprintln(ioStreams.ErrOut, "Cancelled.")
197+
if err != nil {
198+
if cmdutil.IsPromptCancel(err) { // Esc/Ctrl+C = clean cancel
199+
_, _ = fmt.Fprintln(ioStreams.ErrOut, "Canceled.")
200+
return nil
201+
}
202+
return err // real I/O failure must propagate
203+
}
204+
if !confirmed {
205+
_, _ = fmt.Fprintln(ioStreams.ErrOut, "Canceled.")
149206
return nil
150207
}
151208
```
152209

210+
(Note American spelling: `Canceled`. In `--agent` mode require `--yes` and never
211+
prompt; without it, return `cmdutil.NewConfirmationRequiredError(<verb>)`.)
212+
153213
For dangerous actions, add warning styling:
154214

155215
```go

.github/workflows/ci.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
- name: Set up Go
2626
uses: actions/setup-go@v6
2727
with:
28-
go-version: "1.25.9"
28+
go-version-file: go.mod
2929
cache: true
3030

3131
- name: Add Go bin to PATH
@@ -47,7 +47,7 @@ jobs:
4747
- name: Set up Go
4848
uses: actions/setup-go@v6
4949
with:
50-
go-version: "1.25.9"
50+
go-version-file: go.mod
5151
cache: true
5252

5353
- name: Run unit tests
@@ -63,7 +63,7 @@ jobs:
6363
- name: Set up Go
6464
uses: actions/setup-go@v6
6565
with:
66-
go-version: "1.25.9"
66+
go-version-file: go.mod
6767
cache: true
6868

6969
- name: Build
@@ -79,7 +79,7 @@ jobs:
7979
- name: Set up Go
8080
uses: actions/setup-go@v6
8181
with:
82-
go-version: "1.25.9"
82+
go-version-file: go.mod
8383
cache: true
8484

8585
- name: Add Go bin to PATH
@@ -107,7 +107,7 @@ jobs:
107107
- name: Set up Go
108108
uses: actions/setup-go@v6
109109
with:
110-
go-version: "1.25.9"
110+
go-version-file: go.mod
111111
cache: true
112112

113113
- name: Check go mod tidy
@@ -129,7 +129,7 @@ jobs:
129129
- name: Set up Go
130130
uses: actions/setup-go@v6
131131
with:
132-
go-version: "1.25.9"
132+
go-version-file: go.mod
133133
cache: true
134134

135135
- name: Add Go bin to PATH

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
- name: Set up Go
3535
uses: actions/setup-go@v6
3636
with:
37-
go-version: "1.25.9"
37+
go-version-file: go.mod
3838
cache: true
3939

4040
- name: Install git-cliff

.github/workflows/security.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
- name: Set up Go
3232
uses: actions/setup-go@v6
3333
with:
34-
go-version: "1.25.9"
34+
go-version-file: go.mod
3535
cache: true
3636

3737
- name: Add Go bin to PATH
@@ -60,7 +60,7 @@ jobs:
6060
- name: Set up Go
6161
uses: actions/setup-go@v6
6262
with:
63-
go-version: "1.25.9"
63+
go-version-file: go.mod
6464
cache: false
6565

6666
- name: Install gitleaks

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,3 +385,5 @@ docs/plans/
385385

386386
# AI agent project-level configs (installed by users, not shipped)
387387
.cursor/
388+
.claude/skills/
389+
.gitnexus

.pre-commit-config.yaml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ repos:
2525
name: Organize Go imports (goimports)
2626
- id: go-mod-tidy
2727
name: Tidy Go modules (go mod tidy)
28-
- id: go-unit-tests
29-
name: Run Go unit tests
30-
args: [-short]
3128

3229
- repo: local
3330
hooks:
@@ -37,3 +34,11 @@ repos:
3734
language: system
3835
types: [go]
3936
pass_filenames: false
37+
# Replaces dnephin/pre-commit-golang's go-unit-tests, which hardcodes
38+
# -timeout=30s and trips on legitimately slow tests (cmd/auth, options).
39+
- id: go-unit-tests
40+
name: Run Go unit tests
41+
entry: make test
42+
language: system
43+
types: [go]
44+
pass_filenames: false

AGENTS.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ Skipping these steps leads to pattern violations, broken dual-mode, and pricing
2929
## Execution Rules
3030

3131
- **Follow existing patterns** — find the nearest similar command, match its structure
32+
- **Senior comment style** — default to no comments. Add one only when the WHY is non-obvious (invariant, workaround, gotcha, evolution point). One line, identifier-first. Never narrate WHAT — well-named identifiers do that. Delete comments when the reason expires. See CLAUDE.md "Comment style — write like a senior"
3233
- **Preserve dual mode** — every command must work interactive AND non-interactive. Never build one without the other
34+
- **Interactive hint bar** — every direct `prompter.Select(...)` outside the wizard engine must pass `tui.WithShowHints(true)` (and the equivalent option on `MultiSelect`) so the prompt renders its key hints below the choices. Wizard steps are exempt — the composite already renders the hint bar
35+
- **Ctrl+C exits immediately, no confirmation** — use `cmdutil.IsPromptCancel(err)` to detect either Esc or Ctrl+C and return cleanly. When a flow needs different behavior per key (e.g. a "Back to list / Exit" gate where Esc means back), split with `IsPromptInterrupt(err)` (Ctrl+C) and `IsPromptBack(err)` (Esc). Never show an "Exit?" confirmation dialog — Unix users expect Ctrl+C to be terminal
3336
- **Never modify `verdagostack`** directly — describe needed changes for the maintainer
3437
- **Commit only when asked** — don't auto-commit
3538

@@ -50,6 +53,51 @@ Skipping these steps leads to pattern violations, broken dual-mode, and pricing
5053
- [ ] `make test` passes (runs lint + unit tests)
5154
- [ ] `--help` renders correctly for changed commands
5255
- [ ] Interactive and non-interactive modes both work
56+
- [ ] Interactive Selects pass `tui.WithShowHints(true)` so the hint bar renders
5357
- [ ] No leftover debug code, TODOs, or commented-out blocks
5458

5559
If `make lint` reports issues, fix them *before* announcing completion. See `CLAUDE.md` § "Go House Style" for the patterns that prevent the common hits (http.NoBody, American spelling, reused constants, rangeValCopy, nilerr annotations, etc.).
60+
61+
<!-- gitnexus:start -->
62+
# GitNexus — Code Intelligence
63+
64+
This project is indexed by GitNexus as **verda-cli** (7546 symbols, 19146 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
65+
66+
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
67+
68+
## Always Do
69+
70+
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
71+
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
72+
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
73+
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
74+
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
75+
76+
## Never Do
77+
78+
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
79+
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
80+
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
81+
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
82+
83+
## Resources
84+
85+
| Resource | Use for |
86+
|----------|---------|
87+
| `gitnexus://repo/verda-cli/context` | Codebase overview, check index freshness |
88+
| `gitnexus://repo/verda-cli/clusters` | All functional areas |
89+
| `gitnexus://repo/verda-cli/processes` | All execution flows |
90+
| `gitnexus://repo/verda-cli/process/{name}` | Step-by-step execution trace |
91+
92+
## CLI
93+
94+
| Task | Read this skill file |
95+
|------|---------------------|
96+
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
97+
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
98+
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
99+
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
100+
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
101+
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
102+
103+
<!-- gitnexus:end -->

CLAUDE.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,15 @@ The repo lints with `golangci-lint` via `make lint` (included in `make test`). T
8484

8585
`.golangci.yaml` is the authoritative list — all of the above come from linters enabled there.
8686

87+
### Comment style — write like a senior
88+
89+
- **Default to no comment.** Well-named identifiers carry the meaning. Add a comment only when the *why* is non-obvious: an invariant, a workaround, a gotcha a future reader would miss, an evolution point.
90+
- **One line, identifier-first.** `// resolveContainerName: args[0], else picker; agent requires <name>.` beats a three-line paragraph.
91+
- **Never narrate WHAT.** `// Loop over deployments and build labels` is noise — delete it.
92+
- **Capture invariants, not history.** `// Describe still succeeds if status RPC fails.` is durable. `// Added for ticket VC-1234` rots — put it in the commit message.
93+
- **Flag known evolution points.** `// if SDK gains json:"status", switch to explicit fields.` documents a future-failure mode so the next reader doesn't have to rediscover it.
94+
- **Delete when the reason expires.** Workaround landed, gotcha fixed, SDK gap closed → remove the comment in the same commit.
95+
8796
### Every API-calling command MUST:
8897

8998
1. **Timeout context**: `ctx, cancel := context.WithTimeout(cmd.Context(), f.Options().Timeout)`
@@ -98,6 +107,12 @@ The repo lints with `golangci-lint` via `make lint` (included in `make test`). T
98107
- Require `prompter.Confirm()` — return nil on cancel or Esc
99108
- In agent mode (`f.AgentMode()`): require `--yes` flag, never auto-confirm
100109

110+
### Interactive commands MUST:
111+
112+
- **Show the hint bar at the bottom of every direct `Prompter.Select`** — pass `tui.WithShowHints(true)` to render `↑/↓ navigate · type to filter · enter select · esc back · ctrl+c exit` below the choices. Same for `MultiSelect` via the equivalent option. Wizard step Loaders are exempt — the wizard composite already renders its own hint bar; double-rendering is a bug.
113+
- **Treat Ctrl+C as a hard exit, Esc as a soft back** — never show a confirmation dialog on either. Unix users expect Ctrl+C to be terminal; an "Exit?" prompt is friction, and confirmation dialogs themselves can be cancelled which makes the design contradictory. Use `cmdutil.IsPromptInterrupt(err)` for Ctrl+C and `cmdutil.IsPromptBack(err)` for Esc when the two need different handling (e.g. in a "Back to list / Exit" gate, Esc returns to the list while Ctrl+C exits the whole loop). Both are cleanly distinguishable via `cmdutil.IsPromptCancel(err)` if a flow doesn't care which key triggered it.
114+
- **Use `cmdutil.IsPromptCancel(err)`** — never bare-`return nil` on prompter errors; distinguish clean Ctrl+C / Esc from real I/O failures and propagate the latter.
115+
101116
### Pricing — get this wrong and users get billed wrong:
102117

103118
- Instance `price_per_hour` from API is **per-unit** (per-GPU or per-vCPU)
@@ -154,3 +169,47 @@ If you modified a command, also verify:
154169
## Other Agents
155170

156171
This repo targets Claude Code and OpenAI Codex. Claude auto-loads this file; Codex auto-loads `AGENTS.md` (execution contract). A `.cursor/rules/main.mdc` pointer exists for Cursor users but is not a primary target — if Cursor drops out of the stack, delete it rather than letting it drift.
172+
173+
<!-- gitnexus:start -->
174+
# GitNexus — Code Intelligence
175+
176+
This project is indexed by GitNexus as **verda-cli** (7546 symbols, 19146 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
177+
178+
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
179+
180+
## Always Do
181+
182+
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
183+
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
184+
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
185+
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
186+
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
187+
188+
## Never Do
189+
190+
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
191+
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
192+
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
193+
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
194+
195+
## Resources
196+
197+
| Resource | Use for |
198+
|----------|---------|
199+
| `gitnexus://repo/verda-cli/context` | Codebase overview, check index freshness |
200+
| `gitnexus://repo/verda-cli/clusters` | All functional areas |
201+
| `gitnexus://repo/verda-cli/processes` | All execution flows |
202+
| `gitnexus://repo/verda-cli/process/{name}` | Step-by-step execution trace |
203+
204+
## CLI
205+
206+
| Task | Read this skill file |
207+
|------|---------------------|
208+
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
209+
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
210+
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
211+
| Rename / extract / split / refactor | `.claude/skills/gitnexus/gitnexus-refactoring/SKILL.md` |
212+
| Tools, resources, schema reference | `.claude/skills/gitnexus/gitnexus-guide/SKILL.md` |
213+
| Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` |
214+
215+
<!-- gitnexus:end -->

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/verda-cloud/verda-cli
22

3-
go 1.25.9
3+
go 1.25.10
44

55
require (
66
charm.land/lipgloss/v2 v2.0.2
@@ -9,7 +9,7 @@ require (
99
github.com/spf13/pflag v1.0.10
1010
github.com/spf13/viper v1.21.0
1111
github.com/verda-cloud/verdacloud-sdk-go v1.4.2
12-
github.com/verda-cloud/verdagostack v1.3.3
12+
github.com/verda-cloud/verdagostack v1.4.1
1313
go.yaml.in/yaml/v3 v3.0.4
1414
)
1515

@@ -99,6 +99,6 @@ require (
9999
go.uber.org/multierr v1.10.0 // indirect
100100
go.uber.org/zap v1.27.1 // indirect
101101
golang.org/x/sync v0.20.0 // indirect
102-
golang.org/x/sys v0.43.0 // indirect
103-
golang.org/x/text v0.35.0 // indirect
102+
golang.org/x/sys v0.45.0 // indirect
103+
golang.org/x/text v0.36.0 // indirect
104104
)

0 commit comments

Comments
 (0)