Skip to content

Commit 9008a29

Browse files
feat(cli): operate-verb parity with MCP + Homebrew tap publishing (#30)
Wave-2 A4. Adds the day-2 operate surface the MCP server shipped in v0.9 (vault, env-patch, presign, pause, resume, rotate, wake, capabilities, deploy-events) plus the backups read/write surface, so agents no longer fall back to curl after provisioning. Endpoints mirror mcp/src/client.ts exactly; path fragments are named constants. New commands: instant vault set <env> <key> PUT /api/v1/vault/:env/:key instant vault rotate <env> <key> POST /api/v1/vault/:env/:key/rotate instant deploy env <id> K=V... PATCH /deploy/:id/env instant deploy wake <id> POST /deploy/:id/wake instant deploy events <id> GET /api/v1/deployments/:id/events instant stack env <slug> K=V... PATCH /stacks/:slug/env instant storage presign <token> POST /storage/:token/presign instant capabilities GET /api/v1/capabilities instant resource pause|resume POST /api/v1/resources/:token/{pause,resume} instant resource rotate <token> POST /api/v1/resources/:token/rotate-credentials instant resource backup <token> POST /api/v1/resources/:token/backup instant resource backups <token> GET /api/v1/resources/:token/backups Auth contract matches MCP: everything requires a token except storage presign (broker mode — the storage token in the URL is the credential) and capabilities (public pre-flight discovery). Every command supports --json; errors flow through wrapJSONErr. Homebrew tap: - brews block publishing the 'instant' formula to InstaNode-dev/homebrew-tap (repo created this sprint) - release.yml passes HOMEBREW_TAP_TOKEN through to goreleaser - skip_upload template guard: until the operator mints a repo-scoped PAT on the tap and sets the HOMEBREW_TAP_TOKEN secret, the formula push is skipped WITHOUT failing the release (verified via 'goreleaser release --snapshot' dry run) VERSION bumped to 0.3.0 for the release cut after merge. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent d206922 commit 9008a29

11 files changed

Lines changed: 1622 additions & 11 deletions

File tree

.github/workflows/release.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,11 @@ jobs:
7171
# NOT a long-lived PAT. GoReleaser uses it to upload the
7272
# release artifacts to the same repo.
7373
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
74+
# HOMEBREW_TAP_TOKEN pushes the brew formula to
75+
# InstaNode-dev/homebrew-tap (a cross-repo write the per-job
76+
# GITHUB_TOKEN cannot do — it needs an operator-minted PAT with
77+
# `repo` scope on the tap). When the secret is unset this
78+
# resolves to "" and the brews.skip_upload guard in
79+
# .goreleaser.yml skips the formula push WITHOUT failing the
80+
# release.
81+
HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ bin/
66

77
# GoReleaser snapshot/release artifacts
88
dist/
9+
10+
# Local coverage artifacts (CI regenerates these)
11+
coverage.out
12+
coverage.xml

.goreleaser.yml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,41 @@ sboms:
121121
- id: instant-sbom
122122
artifacts: archive
123123

124+
# Homebrew tap (Wave-2 A4). Publishes a formula to InstaNode-dev/homebrew-tap
125+
# so `brew tap InstaNode-dev/tap && brew install instant` works.
126+
#
127+
# TOKEN SHAPE: pushing to the tap repo needs a PAT with `repo` scope on
128+
# InstaNode-dev/homebrew-tap, stored as the HOMEBREW_TAP_TOKEN secret on
129+
# THIS repo (the per-job GITHUB_TOKEN is scoped to InstaNode-dev/cli and
130+
# cannot push to the tap). Until the operator mints that PAT and runs
131+
# `gh secret set HOMEBREW_TAP_TOKEN -R InstaNode-dev/cli`, the skip_upload
132+
# guard below turns the formula push into a no-op — the release itself
133+
# still succeeds (binaries, checksums, signatures, SBOMs all land).
134+
# envOrDefault keeps local `goreleaser release` runs (no env var at all)
135+
# from failing template evaluation.
136+
brews:
137+
- name: instant
138+
repository:
139+
owner: InstaNode-dev
140+
name: homebrew-tap
141+
branch: main
142+
token: '{{ envOrDefault "HOMEBREW_TAP_TOKEN" "" }}'
143+
directory: Formula
144+
homepage: https://instanode.dev
145+
description: "instanode.dev CLI — zero-friction developer infrastructure"
146+
license: MIT
147+
commit_author:
148+
name: instanode-release-bot
149+
email: noreply@instanode.dev
150+
commit_msg_template: "chore: brew formula update for {{ .ProjectName }} {{ .Tag }}"
151+
install: |
152+
bin.install "instant"
153+
test: |
154+
system "#{bin}/instant", "--version"
155+
# Resilient-by-default: skip the tap push (NOT the release) when the
156+
# operator hasn't created HOMEBREW_TAP_TOKEN yet.
157+
skip_upload: '{{ if envOrDefault "HOMEBREW_TAP_TOKEN" "" }}false{{ else }}true{{ end }}'
158+
124159
release:
125160
github:
126161
owner: InstaNode-dev

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.2.0
1+
0.3.0

cmd/deploy_stub.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,13 @@ var deployCmd = &cobra.Command{
4343
// The Short string surfaces in the root command list — that one row is
4444
// the agent's first signal, so it has to carry the "use MCP or curl"
4545
// pointer.
46-
Short: "[stub — current MCP/API path: POST /deploy/new or create_deploy via MCP. CLI deploy verbs not yet implemented]",
47-
Long: `Deploy commands are not implemented in the CLI yet.
46+
Short: "[stub for build verbs — POST /deploy/new or create_deploy via MCP; native: env, wake, events]",
47+
Long: `Deploy BUILD commands (new/list/get/logs/redeploy/delete) are not
48+
implemented in the CLI yet. The operate verbs ARE native:
49+
50+
instant deploy env <app-id> KEY=VALUE… Merge env vars (PATCH /deploy/:id/env)
51+
instant deploy wake <app-id> Wake a scaled-to-zero app (POST /deploy/:id/wake)
52+
instant deploy events <app-id> Failure timeline (GET /api/v1/deployments/:id/events)
4853
4954
The platform exposes the full deploy API at:
5055
POST /deploy/new (multipart tarball upload + build)

cmd/donebar_command_coverage_test.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ var commandTestMap = map[string]string{
9595
"instant deploy redeploy": "TestDeployStub_KnownVerbs",
9696
"instant deploy delete": "TestDeployStub_KnownVerbs",
9797

98+
// ── operate verbs (Wave-2 A4 — MCP parity, operate.go) ───────────────────
99+
"instant vault": "TestOperate_BareGroupsPrintHelp",
100+
"instant vault set": "TestOperate_VaultSet",
101+
"instant vault rotate": "TestOperate_VaultRotate",
102+
"instant stack": "TestOperate_BareGroupsPrintHelp",
103+
"instant stack env": "TestOperate_StackEnvPatch",
104+
"instant deploy env": "TestOperate_DeployEnvPatch",
105+
"instant deploy wake": "TestOperate_DeployWake",
106+
"instant deploy events": "TestOperate_DeployEvents",
107+
"instant storage presign": "TestOperate_StoragePresign",
108+
"instant capabilities": "TestOperate_Capabilities",
109+
98110
// ── bundle / manifest ────────────────────────────────────────────────────
99111
"instant up": "TestIntegration_UpProvisionsAndReconciles",
100112

cmd/extras.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,29 +66,42 @@ var vectorNewCmd = &cobra.Command{
6666
// stays the list view). `instant resource <token>` shows one resource;
6767
// `instant resource delete <token>` tears it down.
6868
var resourceCmd = &cobra.Command{
69-
Use: "resource <token> | delete <token>",
70-
Short: "Show or delete a single resource by token",
71-
Long: `Show or delete a single resource by token.
69+
Use: "resource <token> | <verb> <token>",
70+
Short: "Show, delete, or operate on a single resource by token",
71+
Long: `Show, delete, or operate on a single resource by token.
7272
7373
instant resource <token> Print the resource's metadata + connection URL
7474
instant resource delete <token> Tear down the resource. Requires --yes
7575
(or an interactive 'y' confirmation) to
7676
actually delete — printing nothing
7777
destructive when --yes is absent.
78+
instant resource pause <token> Suspend without deleting (POST …/pause, Pro+)
79+
instant resource resume <token> Un-pause (POST …/resume, Pro+)
80+
instant resource rotate <token> Rotate credentials; prints the NEW
81+
connection URL (POST …/rotate-credentials)
82+
instant resource backup <token> Queue an ad-hoc backup (POST …/backup, tier-gated)
83+
instant resource backups <token> List backups (GET …/backups)
7884
7985
The token argument is the bearer token returned by 'instant <type> new' or
8086
listed in 'instant resources'.`,
8187
Args: cobra.MinimumNArgs(1),
8288
RunE: func(cmd *cobra.Command, args []string) error {
83-
// `instant resource delete <token>` routes here via the same parent;
84-
// peel the verb off and dispatch. Done at RunE rather than as a real
85-
// sub-sub-command so the natural `instant resource <token>` (no
89+
// `instant resource <verb> <token>` routes here via the same parent;
90+
// peel the verb off and dispatch. Done at RunE rather than as real
91+
// sub-sub-commands so the natural `instant resource <token>` (no
8692
// verb) reads as the detail view.
87-
if args[0] == "delete" {
93+
switch verb := args[0]; verb {
94+
case "delete":
8895
if len(args) < 2 {
8996
return wrapJSONErr(cmd, fmt.Errorf("instant resource delete: token argument is required"))
9097
}
9198
return wrapJSONErr(cmd, runResourceDelete(cmd, args[1]))
99+
case "pause", "resume", "rotate", "backup", "backups":
100+
// Wave-2 A4 operate verbs — handlers live in operate.go.
101+
if len(args) < 2 {
102+
return wrapJSONErr(cmd, fmt.Errorf("instant resource %s: token argument is required", verb))
103+
}
104+
return wrapJSONErr(cmd, runResourceOperate(verb, args[1]))
92105
}
93106
return wrapJSONErr(cmd, runResourceDetail(cmd, args[0]))
94107
},

0 commit comments

Comments
 (0)