Skip to content

Commit 372d26b

Browse files
committed
Add CLI updater CI job
1 parent c53898b commit 372d26b

1 file changed

Lines changed: 316 additions & 0 deletions

File tree

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
name: Update CLI Coverage
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
inputs:
8+
pr_number:
9+
description: 'PR number to use for context (leave empty to use most recent merged PR)'
10+
required: false
11+
type: string
12+
13+
permissions:
14+
contents: read
15+
16+
jobs:
17+
update-cli-coverage:
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Generate app token
21+
id: app-token
22+
uses: actions/create-github-app-token@v1
23+
with:
24+
app-id: ${{ secrets.ADMIN_APP_ID }}
25+
private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }}
26+
owner: kernel
27+
28+
- name: Get PR info for manual dispatch
29+
id: pr-info
30+
if: github.event_name == 'workflow_dispatch'
31+
env:
32+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
33+
run: |
34+
if [ -n "${{ inputs.pr_number }}" ]; then
35+
# Use provided PR number
36+
PR_NUMBER="${{ inputs.pr_number }}"
37+
echo "Using provided PR number: $PR_NUMBER"
38+
else
39+
# Get most recent merged PR
40+
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --state merged --limit 1 --json number --jq '.[0].number')
41+
echo "Using most recent merged PR: $PR_NUMBER"
42+
fi
43+
44+
if [ -z "$PR_NUMBER" ]; then
45+
echo "No PR found, will use HEAD commit"
46+
echo "has_pr=false" >> $GITHUB_OUTPUT
47+
else
48+
# Get PR details
49+
PR_DATA=$(gh pr view "$PR_NUMBER" --repo ${{ github.repository }} --json mergeCommit,author,title)
50+
MERGE_SHA=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid // empty')
51+
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login // empty')
52+
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // empty')
53+
54+
echo "PR #$PR_NUMBER: $PR_TITLE"
55+
echo "Merge commit: $MERGE_SHA"
56+
echo "Author: $PR_AUTHOR"
57+
58+
echo "has_pr=true" >> $GITHUB_OUTPUT
59+
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
60+
echo "merge_sha=$MERGE_SHA" >> $GITHUB_OUTPUT
61+
echo "pr_author=$PR_AUTHOR" >> $GITHUB_OUTPUT
62+
fi
63+
64+
- name: Checkout SDK repo
65+
uses: actions/checkout@v4
66+
with:
67+
fetch-depth: 2
68+
fetch-tags: true
69+
# For manual dispatch with a specific PR, checkout the merge commit
70+
ref: ${{ steps.pr-info.outputs.merge_sha || github.sha }}
71+
72+
- name: Install Cursor CLI
73+
run: |
74+
curl https://cursor.com/install -fsS | bash
75+
echo "$HOME/.cursor/bin" >> $GITHUB_PATH
76+
77+
- name: Configure git identity
78+
run: |
79+
git config --global user.name "kernel-internal[bot]"
80+
git config --global user.email "260533166+kernel-internal[bot]@users.noreply.github.com"
81+
82+
- name: Setup Go
83+
uses: actions/setup-go@v6
84+
with:
85+
go-version: 'stable'
86+
87+
- name: Clone API repo
88+
env:
89+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
90+
run: |
91+
gh repo clone kernel/hypeman /tmp/hypeman-api -- --depth=1
92+
93+
- name: Clone CLI repo and checkout existing branch
94+
env:
95+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
96+
run: |
97+
gh repo clone kernel/cli /tmp/kernel-cli
98+
cd /tmp/kernel-cli
99+
100+
# Check if the cli-coverage-update branch already exists on remote
101+
if git ls-remote --exit-code --heads origin cli-coverage-update >/dev/null 2>&1; then
102+
echo "Branch cli-coverage-update exists, checking it out..."
103+
git fetch origin cli-coverage-update
104+
git checkout cli-coverage-update
105+
# Merge latest main to keep it up to date
106+
git merge origin/main -m "Merge main into cli-coverage-update" --no-edit || true
107+
else
108+
echo "Branch cli-coverage-update does not exist, will create from main"
109+
fi
110+
111+
- name: Get SDK version info
112+
id: sdk-version
113+
run: |
114+
# Get the latest tag if available, otherwise use commit SHA
115+
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
116+
if [ -n "$LATEST_TAG" ]; then
117+
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
118+
echo "SDK version: $LATEST_TAG"
119+
else
120+
CURRENT_SHA="${{ steps.pr-info.outputs.merge_sha || github.sha }}"
121+
echo "version=$CURRENT_SHA" >> $GITHUB_OUTPUT
122+
echo "SDK version: $CURRENT_SHA (no tag)"
123+
fi
124+
125+
# Get the module path from go.mod
126+
MODULE_PATH=$(head -1 go.mod | awk '{print $2}')
127+
echo "module=$MODULE_PATH" >> $GITHUB_OUTPUT
128+
echo "SDK module: $MODULE_PATH"
129+
130+
# Determine the commit author (from PR info for manual dispatch, or from push event)
131+
if [ -n "${{ steps.pr-info.outputs.pr_author }}" ]; then
132+
echo "author=${{ steps.pr-info.outputs.pr_author }}" >> $GITHUB_OUTPUT
133+
else
134+
echo "author=${{ github.event.head_commit.author.username || github.actor }}" >> $GITHUB_OUTPUT
135+
fi
136+
137+
- name: Update CLI coverage
138+
env:
139+
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
140+
GH_TOKEN: ${{ steps.app-token.outputs.token }}
141+
BRANCH_PREFIX: cli-coverage-update
142+
run: |
143+
cursor-agent -p "You are a CLI updater that implements missing CLI commands based on SDK updates.
144+
145+
The GitHub CLI is available as \`gh\` and authenticated via GH_TOKEN. Git is available. You have write access to the CLI repository (kernel/cli).
146+
147+
# Context
148+
- SDK Repo: ${{ github.repository }} (current directory)
149+
- SDK Module: ${{ steps.sdk-version.outputs.module }}
150+
- SDK Version: ${{ steps.sdk-version.outputs.version }}
151+
- Commit SHA: ${{ steps.pr-info.outputs.merge_sha || github.sha }}
152+
- Commit Author: ${{ steps.sdk-version.outputs.author }}
153+
- Trigger: ${{ github.event_name }} ${{ inputs.pr_number && format('(PR #{0})', inputs.pr_number) || '' }}
154+
- API Repo Location: /tmp/hypeman-api
155+
- CLI Repo Location: /tmp/kernel-cli
156+
- Update Branch Prefix: cli-coverage-update
157+
158+
# Background
159+
The Go SDK (this repo) was just updated by Stainless, and may contain new API methods. The CLI (kernel/cli) needs to be updated to expose these new methods as CLI commands.
160+
161+
# Source Files
162+
- SDK api.md: Current directory - READ THIS FILE FIRST. This is the authoritative list of all SDK methods and their signatures.
163+
- SDK *.go files: Current directory - Contains param structs (e.g., InstanceNewParams, ImageNewParams) with all available options/fields.
164+
- API Spec: /tmp/hypeman-api/packages/api/stainless.yaml - SDK configuration with resources and methods
165+
- API Spec: /tmp/hypeman-api/packages/api/openapi.yaml - Full OpenAPI specification. CHECK for x-cli-skip: true on endpoints - skip those from CLI coverage.
166+
- CLI: /tmp/kernel-cli - Existing CLI commands
167+
168+
# Task
169+
170+
## Step 1: Update SDK Version (ALWAYS DO THIS FIRST)
171+
- Go to /tmp/kernel-cli
172+
- Update go.mod to require the latest SDK: ${{ steps.sdk-version.outputs.module }}@${{ steps.sdk-version.outputs.version }}
173+
- Run: go get ${{ steps.sdk-version.outputs.module }}@${{ steps.sdk-version.outputs.version }}
174+
- Run: go mod tidy
175+
- This ensures the CLI always uses the latest SDK, even if no new commands are added
176+
177+
## Step 2: Full SDK Method Enumeration (CRITICAL - DO NOT SKIP)
178+
You MUST perform a complete enumeration of ALL SDK methods and their parameters. Do NOT rely only on recent commits.
179+
180+
2a. Read the api.md file in the SDK repo root. This file lists EVERY SDK method in the format:
181+
- \`client.Resource.Method(ctx, params)\` with links to param types
182+
Extract a complete list of all methods.
183+
184+
2b. For EACH SDK method, read the corresponding param type from the Go source files.
185+
For example:
186+
- InstanceNewParams in instance.go -> lists all fields like \`Image\`, \`Region\`, \`VolumeMounts\`, etc.
187+
- ImageNewParams in image.go -> lists all fields like \`Name\`, \`Tag\`, etc.
188+
- VolumeNewParams in volume.go -> lists all fields like \`Name\`, \`Size\`, etc.
189+
Each field in a Params struct represents an option that could be a CLI flag.
190+
191+
2c. Build a complete SDK coverage matrix:
192+
| SDK Method | SDK Param Type | SDK Param Fields |
193+
|------------|----------------|------------------|
194+
| client.Instances.New | InstanceNewParams | Image, Region, VolumeMounts, ... |
195+
| client.Instances.List | (none) | |
196+
| client.Images.New | ImageNewParams | Name, Tag, ... |
197+
| client.Volumes.New | VolumeNewParams | Name, Size, ... |
198+
| ... | ... | ... |
199+
200+
## Step 3: Full CLI Command Enumeration (CRITICAL - DO NOT SKIP)
201+
Enumerate ALL existing CLI commands and their flags.
202+
203+
3a. Look at cmd/ directory structure for existing commands
204+
3b. For each command file, extract:
205+
- The command name/path (e.g., \`kernel instance create\`)
206+
- All flags defined for that command
207+
3c. Build a CLI coverage matrix:
208+
| CLI Command | CLI Flags |
209+
|-------------|-----------|
210+
| kernel instance create | --image, --region, ... |
211+
| kernel instance list | |
212+
| ... | ... |
213+
214+
## Step 4: Gap Analysis (CRITICAL - DO NOT SKIP)
215+
Compare the SDK matrix (Step 2) with the CLI matrix (Step 3) to identify:
216+
217+
4a. Missing commands: SDK methods with NO corresponding CLI command
218+
4b. Missing flags: SDK param fields with NO corresponding CLI flag
219+
4c. Create a gap report:
220+
## Missing Commands
221+
- client.Instances.Restore -> needs \`kernel instance restore\`
222+
- client.Devices.ListAvailable -> needs \`kernel device list-available\`
223+
224+
## Missing Flags
225+
- InstanceNewParams.SomeNewField -> \`kernel instance create\` needs --some-new-field
226+
- VolumeNewParams.Region -> \`kernel volume create\` needs --region
227+
228+
## Step 5: Implement Missing Coverage
229+
For each gap identified in Step 4:
230+
- Implement missing commands following existing patterns
231+
- Add missing flags to existing commands
232+
- Run \`go build ./...\` to verify the code compiles
233+
234+
## Step 6: Commit and Push
235+
- You should already be on the cli-coverage-update branch (it was checked out during setup if it existed)
236+
- If you're on main, create/switch to the cli-coverage-update branch
237+
- Commit with message describing SDK version bump and any new commands/flags
238+
- IMPORTANT: Do NOT force push! Use regular \`git push origin cli-coverage-update\` to preserve existing work on the branch
239+
- If push fails due to divergence, pull and rebase first: \`git pull --rebase origin cli-coverage-update\`
240+
- Create or update the PR in kernel/cli
241+
242+
# SDK Method -> CLI Command Mapping Guide
243+
- client.Resource.New() -> kernel resource create
244+
- client.Resource.List() -> kernel resource list
245+
- client.Resource.Get() -> kernel resource get
246+
- client.Resource.Delete() -> kernel resource delete
247+
- client.Resource.Update() -> kernel resource update
248+
- client.Resource.Sub.Action() -> kernel resource sub action
249+
- client.Resource.CustomAction() -> kernel resource custom-action
250+
251+
# SDK Param Field -> CLI Flag Mapping Guide
252+
- CamelCaseField -> --camel-case-field
253+
- TimeoutSeconds -> --timeout-seconds
254+
- IncludeDeleted -> --include-deleted
255+
- Nested structs: VolumeMount.Path -> --volume-mount-path or separate flags
256+
257+
# Implementation Guidelines
258+
- Follow the existing CLI code patterns in /tmp/kernel-cli
259+
- Use cobra for command definitions
260+
- Use the Hypeman Go SDK (this repo) for API calls
261+
- Include proper flag definitions with descriptions matching SDK field comments
262+
- Add help text for commands matching SDK method comments
263+
- Handle errors appropriately
264+
- Match the style of existing commands
265+
266+
# Output Format
267+
After pushing changes, create or update an evergreen PR using gh:
268+
269+
1. Check if a PR already exists for the cli-coverage-update branch:
270+
gh pr list --repo kernel/cli --head cli-coverage-update --json number
271+
272+
2. If PR exists, update it. If not, create a new one.
273+
274+
If new commands or flags were added:
275+
Title: 'CLI: Update hypeman SDK to <version> and add new commands/flags'
276+
Body:
277+
'This PR updates the Hypeman Go SDK to ${{ steps.sdk-version.outputs.version }} and adds CLI commands/flags for new SDK methods.
278+
279+
## SDK Update
280+
- Updated hypeman-go to ${{ steps.sdk-version.outputs.version }}
281+
282+
## Coverage Analysis
283+
This PR was generated by performing a full enumeration of SDK methods and CLI commands.
284+
285+
## New Commands
286+
- \`kernel <resource> <action>\` for \`client.Resource.Action()\`
287+
288+
## New Flags
289+
- \`--flag-name\` for \`ResourceParams.FieldName\`
290+
291+
Triggered by: kernel/hypeman-go@${{ steps.pr-info.outputs.merge_sha || github.sha }}
292+
Reviewer: @<commit_author>'
293+
294+
If only SDK version update (no coverage gaps found):
295+
Title: 'CLI: Update Hypeman Go SDK to ${{ steps.sdk-version.outputs.version }}'
296+
Body:
297+
'This PR updates the Hypeman Go SDK dependency to the latest version.
298+
299+
## SDK Update
300+
- Updated hypeman-go to ${{ steps.sdk-version.outputs.version }}
301+
302+
## Coverage Analysis
303+
A full enumeration of SDK methods and CLI commands was performed. No coverage gaps were found.
304+
305+
Triggered by: kernel/hypeman-go@${{ steps.pr-info.outputs.merge_sha || github.sha }}
306+
Reviewer: @<commit_author>'
307+
308+
# Constraints
309+
- ALWAYS update the SDK version in go.mod - this is the primary purpose
310+
- ALWAYS perform the full enumeration (Steps 2-4) - this is critical for finding gaps
311+
- ALL SDK methods in api.md MUST have corresponding CLI commands, EXCEPT those marked with x-cli-skip in openapi.yaml
312+
- SKIP endpoints marked with x-cli-skip: true in openapi.yaml - these are internal endpoints not suitable for CLI
313+
- Streaming methods may have different CLI implementations (e.g., follow flags)
314+
- Even if no coverage gaps are found, still create a PR for the SDK version bump
315+
- Ensure code compiles before pushing
316+
" --model opus-4.6 --force --output-format=text

0 commit comments

Comments
 (0)