Repository navigation
676 lines (611 loc) · 34.8 KB
/
Copy pathupdate-cli-coverage.yml
File metadata and controls
676 lines (611 loc) · 34.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
name: Update CLI Coverage
on:
# A push to main can run before release-please tags the release.
release:
types: [published]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to use for context (leave empty to use most recent merged PR)'
required: false
type: string
permissions:
contents: read
concurrency:
group: update-cli-coverage
cancel-in-progress: false
jobs:
prefetch:
if: github.repository == 'kernel/hypeman-go'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
pull-requests: read
outputs:
sha: ${{ steps.pr-info.outputs.merge_sha || github.sha }}
version: ${{ steps.sdk-version.outputs.version }}
module: ${{ steps.sdk-version.outputs.module }}
author: ${{ steps.sdk-version.outputs.author }}
old_version: ${{ steps.sdk-diff.outputs.old_version }}
steps:
- name: Generate API read token
id: api-read-token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ secrets.ADMIN_APP_ID }}
private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }}
owner: kernel
repositories: hypeman
permission-contents: read
- name: Get PR info for manual dispatch
id: pr-info
if: github.event_name == 'workflow_dispatch'
env:
GH_TOKEN: ${{ github.token }}
run: |
if [ -n "${{ inputs.pr_number }}" ]; then
# Use provided PR number
PR_NUMBER="${{ inputs.pr_number }}"
echo "Using provided PR number: $PR_NUMBER"
else
# Get most recent merged PR
PR_NUMBER=$(gh pr list --repo ${{ github.repository }} --state merged --limit 1 --json number --jq '.[0].number')
echo "Using most recent merged PR: $PR_NUMBER"
fi
if [ -z "$PR_NUMBER" ]; then
echo "No PR found, will use HEAD commit"
echo "has_pr=false" >> $GITHUB_OUTPUT
else
# Get PR details
PR_DATA=$(gh pr view "$PR_NUMBER" --repo ${{ github.repository }} --json mergeCommit,author,title)
MERGE_SHA=$(echo "$PR_DATA" | jq -r '.mergeCommit.oid // empty')
PR_AUTHOR=$(echo "$PR_DATA" | jq -r '.author.login // empty')
PR_TITLE=$(echo "$PR_DATA" | jq -r '.title // empty')
echo "PR #$PR_NUMBER: $PR_TITLE"
echo "Merge commit: $MERGE_SHA"
echo "Author: $PR_AUTHOR"
echo "has_pr=true" >> $GITHUB_OUTPUT
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "merge_sha=$MERGE_SHA" >> $GITHUB_OUTPUT
echo "pr_author=$PR_AUTHOR" >> $GITHUB_OUTPUT
fi
- name: Checkout SDK repo
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
# The SDK diff starts at CLI main's version, which can be several tags back.
fetch-depth: 0
# For manual dispatch with a specific PR, checkout the merge commit
ref: ${{ steps.pr-info.outputs.merge_sha || github.sha }}
- name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: 'stable'
cache: false
- name: Get SDK version info
id: sdk-version
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
# Use the published release, else the latest tag, else the commit SHA
LATEST_TAG=${RELEASE_TAG:-$(git describe --tags --abbrev=0 2>/dev/null || echo "")}
if [ -n "$LATEST_TAG" ]; then
echo "version=$LATEST_TAG" >> $GITHUB_OUTPUT
echo "SDK version: $LATEST_TAG"
else
CURRENT_SHA="${{ steps.pr-info.outputs.merge_sha || github.sha }}"
echo "version=$CURRENT_SHA" >> $GITHUB_OUTPUT
echo "SDK version: $CURRENT_SHA (no tag)"
fi
# Get the module path from go.mod
MODULE_PATH=$(head -1 go.mod | awk '{print $2}')
echo "module=$MODULE_PATH" >> $GITHUB_OUTPUT
echo "SDK module: $MODULE_PATH"
# Determine the commit author (from PR info for manual dispatch, or from push event)
if [ -n "${{ steps.pr-info.outputs.pr_author }}" ]; then
echo "author=${{ steps.pr-info.outputs.pr_author }}" >> $GITHUB_OUTPUT
else
echo "author=${{ github.event.head_commit.author.username || github.actor }}" >> $GITHUB_OUTPUT
fi
- name: Compute SDK diff since CLI main's version
id: sdk-diff
run: |
context="$RUNNER_TEMP/cli-coverage-context"
mkdir -p "$context/api"
# The report replaces the PR description, so audit every release since
# the SDK version on CLI main, not only the ones since the last run.
git clone -q --depth=1 https://github.com/kernel/hypeman-cli.git "$RUNNER_TEMP/hypeman-cli-main"
OLD_SDK_VERSION=$(go mod edit -json "$RUNNER_TEMP/hypeman-cli-main/go.mod" \
| jq -er '.Require[] | select(.Path == "github.com/kernel/hypeman-go") | .Version')
echo "CLI main uses SDK version: $OLD_SDK_VERSION"
echo "old_version=$OLD_SDK_VERSION" >> $GITHUB_OUTPUT
NEW_SDK_VERSION="${{ steps.sdk-version.outputs.version }}"
echo "New SDK version: $NEW_SDK_VERSION"
# Compute the diff between old and new SDK versions
# This gives the agent a focused view of what changed
if git diff "$OLD_SDK_VERSION".."$NEW_SDK_VERSION" -- api.md '*.go' ':!*_test.go' ':!internal/*' > "$context/sdk-diff.patch" 2>/dev/null; then
DIFF_LINES=$(wc -l < "$context/sdk-diff.patch")
echo "SDK diff: $DIFF_LINES lines"
if [ "$DIFF_LINES" -eq 0 ]; then
echo "No SDK changes between versions (diff is empty)"
fi
else
echo "Could not compute SDK diff (old version tag may not exist), creating empty diff"
echo "" > "$context/sdk-diff.patch"
fi
utc_date() {
TZ=UTC git log -1 --date=format-local:%Y-%m-%dT%H:%M:%SZ --format=%cd "$1"
}
echo "old_date=$(utc_date "$OLD_SDK_VERSION" 2>/dev/null || true)" >> $GITHUB_OUTPUT
echo "new_date=$(utc_date HEAD)" >> $GITHUB_OUTPUT
- name: Fetch API spec
env:
GH_TOKEN: ${{ steps.api-read-token.outputs.token }}
OLD_DATE: ${{ steps.sdk-diff.outputs.old_date }}
NEW_DATE: ${{ steps.sdk-diff.outputs.new_date }}
run: |
context="$RUNNER_TEMP/cli-coverage-context"
main_at() {
gh api -X GET repos/kernel/hypeman/commits -f sha=main -f until="$1" -f per_page=1 --jq '.[0].sha // empty'
}
file_at() {
gh api -H 'Accept: application/vnd.github.raw' "repos/kernel/hypeman/contents/$1?ref=$2"
}
head=$(main_at "$NEW_DATE")
test -n "$head"
for file in openapi.yaml stainless.yaml; do
file_at "$file" "$head" > "$context/api/$file"
done
base=""
if [[ -n "$OLD_DATE" ]]; then
base=$(main_at "$OLD_DATE")
fi
if [[ -n "$base" ]]; then
file_at openapi.yaml "$base" > "$RUNNER_TEMP/openapi-base.yaml"
diff -u --label "openapi.yaml@$base" --label "openapi.yaml@$head" \
"$RUNNER_TEMP/openapi-base.yaml" "$context/api/openapi.yaml" \
> "$context/openapi-diff.patch" || [[ $? -eq 1 ]]
rm "$RUNNER_TEMP/openapi-base.yaml"
else
printf 'Spec baseline unavailable; compare all relevant descriptions in openapi.yaml.\n' > "$context/openapi-diff.patch"
fi
gh api --method DELETE /installation/token
- name: Upload CLI coverage context
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: cli-coverage-context
path: ${{ runner.temp }}/cli-coverage-context
if-no-files-found: error
retention-days: 7
generate:
needs: prefetch
if: github.repository == 'kernel/hypeman-go'
runs-on: ubuntu-latest
timeout-minutes: 55
permissions:
contents: read
# The model credentials step exchanges the job's OIDC token for short-lived model credentials.
id-token: write
outputs:
changed: ${{ steps.snapshot.outputs.changed }}
main_sha: ${{ steps.cli-clone.outputs.main_sha }}
update_sha: ${{ steps.cli-clone.outputs.update_sha }}
rebased: ${{ steps.cli-clone.outputs.rebased }}
agent_base_tree: ${{ steps.cli-clone.outputs.agent_base_tree }}
steps:
- name: Checkout SDK repo
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
ref: ${{ needs.prefetch.outputs.sha }}
- name: Checkout updater scripts
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
path: updater
sparse-checkout: .github/scripts
persist-credentials: false
- name: Download CLI coverage context
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: cli-coverage-context
path: /tmp/cli-coverage-context
- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: '22'
- name: Install Pi
env:
PI_TELEMETRY: '0'
run: |
npm install -g --ignore-scripts @earendil-works/pi-coding-agent
pi --version
- name: Configure git identity
run: |
git config --global user.name "kernel-internal[bot]"
git config --global user.email "260533166+kernel-internal[bot]@users.noreply.github.com"
- name: Setup Go
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6
with:
go-version: 'stable'
# Pi can write to the Go caches, so they must never be saved for other runs.
cache: false
- name: Clone CLI repo
id: cli-clone
run: |
git clone https://github.com/kernel/hypeman-cli.git /tmp/hypeman-cli
cd /tmp/hypeman-cli
git config user.name "kernel-internal[bot]"
git config user.email "260533166+kernel-internal[bot]@users.noreply.github.com"
echo "main_sha=$(git rev-parse origin/main)" >> "$GITHUB_OUTPUT"
if git show-ref --verify --quiet refs/remotes/origin/cli-coverage-update; then
echo "update_sha=$(git rev-parse origin/cli-coverage-update)" >> "$GITHUB_OUTPUT"
git switch --track origin/cli-coverage-update
if ! git rebase origin/main; then
git rebase --abort
# The agent covers everything since main's SDK version, so a bot-only
# branch can be regenerated from main. Human commits must be kept.
# The last two identities are from the previous agent CLI's runs.
if git log --no-merges --format=%ae origin/main..origin/cli-coverage-update \
| grep -qvxF \
-e '260533166+kernel-internal[bot]@users.noreply.github.com' \
-e 'kernel-internal[bot]@users.noreply.github.com' \
-e 'cursor-agent@cursor.com'; then
echo "::error::cli-coverage-update has human commits that conflict with main. Resolve the conflicts on that branch, then rerun this workflow."
exit 1
fi
echo "::warning::cli-coverage-update conflicts with main; regenerating it from main."
git switch -C cli-coverage-update origin/main
else
echo "rebased=true" >> "$GITHUB_OUTPUT"
fi
else
git switch -c cli-coverage-update origin/main
fi
# The agent's changes are published on top of this head, so human
# commits on the update branch keep their content and authorship.
echo "agent_base=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
echo "agent_base_tree=$(git rev-parse 'HEAD^{tree}')" >> "$GITHUB_OUTPUT"
- name: Model credentials
uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main
with:
model: ${{ vars.PI_PREFERRED_MODEL }}
anthropic-federation-rule-id: ${{ vars.ANTHROPIC_FEDERATION_RULE_ID }}
anthropic-organization-id: ${{ vars.ANTHROPIC_ORGANIZATION_ID }}
anthropic-service-account-id: ${{ vars.ANTHROPIC_SERVICE_ACCOUNT_ID }}
openai-identity-provider-id: ${{ vars.OPENAI_IDENTITY_PROVIDER_ID }}
openai-service-account-id: ${{ vars.OPENAI_SERVICE_ACCOUNT_ID }}
openai-audience: ${{ vars.OPENAI_WIF_AUDIENCE }}
- name: Update CLI coverage
env:
PI_PREFERRED_MODEL: ${{ vars.PI_PREFERRED_MODEL }}
PI_SKIP_VERSION_CHECK: '1'
PI_TELEMETRY: '0'
BRANCH_PREFIX: cli-coverage-update
run: |
: "${PI_PREFERRED_MODEL:?Set the PI_PREFERRED_MODEL organization variable}"
mkdir -p "$RUNNER_TEMP/cli-scratch"
pi --print --no-session --approve \
--model "$PI_PREFERRED_MODEL" --thinking max \
"You are a CLI updater that implements missing CLI commands based on SDK updates.
Git is available for local diffs only. You have no GitHub publishing credentials.
Do not commit, push, open, or merge PRs. The workflow validates and publishes your changes.
# Context
- SDK Repo: ${{ github.repository }} (current directory)
- SDK Module: ${{ needs.prefetch.outputs.module }}
- SDK Version: ${{ needs.prefetch.outputs.version }}
- Commit SHA: ${{ needs.prefetch.outputs.sha }}
- Commit Author: ${{ needs.prefetch.outputs.author }}
- Trigger: ${{ github.event_name }} ${{ inputs.pr_number && format('(PR #{0})', inputs.pr_number) || '' }}
- API Spec Location: /tmp/cli-coverage-context/api (openapi.yaml and stainless.yaml as of this SDK release)
- CLI Repo Location: /tmp/hypeman-cli
- Update Branch Prefix: cli-coverage-update
# Background
The Go SDK (this repo) was just updated by Stainless, and may contain new API methods. The CLI (kernel/hypeman-cli) needs to be updated to expose these new methods as CLI commands.
The CLI main branch is on SDK version ${{ needs.prefetch.outputs.old_version }}, and we are updating it to ${{ needs.prefetch.outputs.version }}.
The update branch may already hold coverage from earlier runs for releases in between; keep it and build on it.
# Source Files
- SDK api.md: Current directory - READ THIS FILE FIRST. This is the authoritative list of all SDK methods and their signatures.
- SDK *.go files: Current directory - Contains param structs (e.g., InstanceNewParams, ImageNewParams) with all available options/fields.
- SDK diff: /tmp/cli-coverage-context/sdk-diff.patch - IMPORTANT: This is the diff of api.md and Go source files between the SDK version on CLI main (${{ needs.prefetch.outputs.old_version }}) and the new version (${{ needs.prefetch.outputs.version }}). Read this file to see exactly what methods, params, and fields were added or changed. Any additions here MUST be reflected in the CLI.
- API Spec: /tmp/cli-coverage-context/api/stainless.yaml - SDK configuration with resources and methods
- API Spec: /tmp/cli-coverage-context/api/openapi.yaml - Full OpenAPI specification. CHECK for x-cli-skip: true on endpoints - skip those from CLI coverage.
- API Spec diff: /tmp/cli-coverage-context/openapi-diff.patch - Spec changes between the SDK version on CLI main and the new one, including endpoint, parameter, and field description changes.
- CLI: /tmp/hypeman-cli - Existing CLI commands (in pkg/cmd/ directory)
# CLI Architecture
The CLI uses urfave/cli/v3 (NOT cobra). Commands are defined in /tmp/hypeman-cli/pkg/cmd/:
- Root command: pkg/cmd/cmd.go
- Resource commands: pkg/cmd/{resource}cmd.go (e.g., imagecmd.go, volumecmd.go, devicecmd.go, ingresscmd.go)
- Top-level commands: pkg/cmd/{command}.go (e.g., run.go, ps.go, rm.go, logs.go, exec.go, cp.go)
- Lifecycle commands: pkg/cmd/lifecycle.go (stop, start, standby, restore)
- Build commands: pkg/cmd/build.go
- Utilities: pkg/cmd/cmdutil.go, pkg/cmd/format.go
- Entry point: cmd/hypeman/main.go
- Compose support: lib/compose/ (also uses the SDK)
# Task
## Step 1: Update SDK Version (ALWAYS DO THIS FIRST)
- Go to /tmp/hypeman-cli
- Update go.mod to require the latest SDK: ${{ needs.prefetch.outputs.module }}@${{ needs.prefetch.outputs.version }}
- Run: go get ${{ needs.prefetch.outputs.module }}@${{ needs.prefetch.outputs.version }}
- Run: go mod tidy
- This ensures the CLI always uses the latest SDK, even if no new commands are added
## Step 2: Review SDK Diff (CRITICAL - DO THIS BEFORE THE FULL ENUMERATION)
Read /tmp/cli-coverage-context/sdk-diff.patch to see exactly what changed in the SDK between the version on CLI main and the new version.
This diff shows new methods, new param fields, and changed signatures — these are the changes most likely to need CLI coverage.
Treat every addition in this diff as a checklist item: each new method needs a CLI command, each new param field needs a CLI flag.
After verifying these are covered, proceed to the full enumeration below to catch anything else.
## Step 3: Full SDK Method Enumeration (CRITICAL - DO NOT SKIP)
You MUST perform a complete enumeration of ALL SDK methods and their parameters. Do NOT rely only on the diff from Step 2.
3a. Read the api.md file in the SDK repo root. This file lists EVERY SDK method in the format:
- \`client.Resource.Method(ctx, params)\` with links to param types
Extract a complete list of all methods.
3b. For EACH SDK method, read the corresponding param type from the Go source files.
For example:
- InstanceNewParams in instance.go -> lists all fields like \`Image\`, \`Region\`, \`VolumeMounts\`, etc.
- ImageNewParams in image.go -> lists all fields like \`Name\`, \`Tag\`, etc.
- VolumeNewParams in volume.go -> lists all fields like \`Name\`, \`Size\`, etc.
Each field in a Params struct represents an option that could be a CLI flag.
3c. Build a complete SDK coverage matrix:
| SDK Method | SDK Param Type | SDK Param Fields |
|------------|----------------|------------------|
| client.Instances.New | InstanceNewParams | Image, Region, VolumeMounts, ... |
| client.Instances.List | (none) | |
| client.Images.New | ImageNewParams | Name, Tag, ... |
| client.Volumes.New | VolumeNewParams | Name, Size, ... |
| ... | ... | ... |
## Step 4: Full CLI Command Enumeration (CRITICAL - DO NOT SKIP)
Enumerate ALL existing CLI commands and their flags.
4a. Look at pkg/cmd/ directory for existing command files
4b. For each command file, extract:
- The command name/path (e.g., \`hypeman run\`, \`hypeman image list\`)
- All flags defined for that command
4c. Build a CLI coverage matrix:
| CLI Command | CLI Flags |
|-------------|-----------|
| hypeman run | --name, --image, --quiet, ... |
| hypeman ps | --quiet, --format, ... |
| hypeman image list | --format, ... |
| hypeman volume create | --name, ... |
| ... | ... |
## Step 5: Gap Analysis (CRITICAL - DO NOT SKIP)
Compare the SDK matrix (Step 3) with the CLI matrix (Step 4) to identify:
5a. Missing commands: SDK methods with NO corresponding CLI command
5b. Missing flags: SDK param fields with NO corresponding CLI flag
5c. Cross-check against the SDK diff from Step 2: every addition in /tmp/cli-coverage-context/sdk-diff.patch MUST appear as a gap to address (new method = missing command, new param field = missing flag) unless it already exists in the CLI.
5d. Create a gap report:
## Missing Commands
- client.SomeResource.SomeMethod -> needs new CLI command
## Missing Flags
- InstanceNewParams.SomeNewField -> \`hypeman run\` needs --some-new-field
- VolumeNewParams.Region -> \`hypeman volume create\` needs --region
## Step 6: Implement Missing Coverage
For each gap identified in Step 5:
- Implement missing commands following existing patterns in pkg/cmd/
- Add missing flags to existing commands
- Use urfave/cli/v3 for command and flag definitions (NOT cobra)
- Run \`go build ./...\` to verify the code compiles
## Step 6b: Sync Help Text With the API Spec
The SDK is generated from openapi.yaml, so its endpoint, parameter, and schema descriptions are the source of truth for what the API does.
- For every changed summary or description in /tmp/cli-coverage-context/openapi-diff.patch that belongs to an endpoint, parameter, or field the CLI exposes, update the matching command Usage/Description text and flag Usage string. Use the spec text for anything you add in this run too.
- Adapt the text; do not copy it verbatim. API descriptions are written for HTTP callers and often reference other endpoints, HTTP methods, status codes, headers, or JSON body paths that mean nothing to a CLI user. Rewrite those references in CLI terms: name the hypeman command and flags that do the equivalent. For example, \"List volumes with \`GET /volumes\`\" becomes \"List volumes with \`hypeman volume list\`\", and a body field like \`size_gb\` becomes its flag, \`--size\`. Drop API details that do not apply to the command.
- Follow existing help text style: keep Usage to one concise line and put caveats in Description.
- Preserve meaning exactly: limits, defaults, units, accepted values, deprecations, and caveats must match the spec. Do not invent behavior the spec does not state.
- Change only text whose meaning the spec change affects, or existing text the spec now shows to be wrong. Do not restyle unrelated help text.
## Step 7: Review and Report
- Keep every scratch artifact outside the checkout: write binaries, command output, and logs under $RUNNER_TEMP/cli-scratch, never under /tmp/hypeman-cli.
- Read the full diff against origin/main and remove unrelated changes or unnecessary wrappers/casts.
- Write $RUNNER_TEMP/cli-coverage-report.md with the new commands/flags, help text updated to match spec changes (with the spec operation or field each follows), and intentional exclusions.
- The report replaces the PR description, so it must describe every change in the diff against origin/main, including changes earlier runs left on this branch, not only this run's.
- The report becomes part of a public PR. Include no credentials, customer information, private discussions, or internal URLs.
- Do not commit, push, change git authentication, or open PRs. Leave the changes for the workflow to validate and publish.
- Only go.mod, go.sum, and .go files under cmd/, pkg/, and lib/ are published. The workflow discards every other change before validation.
# SDK Method -> CLI Command Mapping Guide
Instance operations use Docker-style top-level commands:
- client.Instances.New() -> hypeman run
- client.Instances.List() -> hypeman ps
- client.Instances.Delete() -> hypeman rm
- client.Instances.Get() -> (used internally by other commands)
- client.Instances.Logs() -> hypeman logs
- client.Instances.Stop() -> hypeman stop
- client.Instances.Start() -> hypeman start
- client.Instances.Standby() -> hypeman standby
- client.Instances.Restore() -> hypeman restore
- client.Instances.Stat() -> (used internally by cp)
Resource group commands use subcommands:
- client.Images.New() -> hypeman image create
- client.Images.List() -> hypeman image list
- client.Images.Get() -> hypeman image get
- client.Images.Delete() -> hypeman image delete
- client.Volumes.New() -> hypeman volume create
- client.Volumes.List() -> hypeman volume list
- client.Volumes.Get() -> hypeman volume get
- client.Volumes.Delete() -> hypeman volume delete
- client.Volumes.NewFromArchive() -> (used internally by push)
- client.Instances.Volumes.Attach() -> hypeman volume attach
- client.Instances.Volumes.Detach() -> hypeman volume detach
- client.Devices.New() -> hypeman device register
- client.Devices.List() -> hypeman device list
- client.Devices.Get() -> hypeman device get
- client.Devices.Delete() -> hypeman device delete
- client.Devices.ListAvailable() -> hypeman device available
- client.Ingresses.New() -> hypeman ingress create
- client.Ingresses.List() -> hypeman ingress list
- client.Ingresses.Get() -> hypeman ingress get
- client.Ingresses.Delete() -> hypeman ingress delete
- client.Builds.New() -> hypeman build create (or used internally)
- client.Builds.List() -> hypeman build list
- client.Builds.Get() -> hypeman build get
- client.Builds.Cancel() -> hypeman build cancel
- client.Builds.Events() -> (used internally for streaming build output)
- client.Resources.Get() -> hypeman resources
- client.Health.Check() -> (internal, no CLI needed)
# SDK Param Field -> CLI Flag Mapping Guide
- CamelCaseField -> --camel-case-field
- TimeoutSeconds -> --timeout-seconds
- IncludeDeleted -> --include-deleted
- Optional fields use hypeman.Opt() wrapper in SDK calls
# Implementation Guidelines
- Follow the existing CLI code patterns in /tmp/hypeman-cli/pkg/cmd/
- Use urfave/cli/v3 for command definitions (cli.Command struct with Flags, Action, etc.)
- Use the Hypeman Go SDK (this repo) for API calls: hypeman.NewClient(getDefaultRequestOptions(cmd)...)
- Use existing helpers: ShowJSON() for output, ResolveInstance() for name resolution, FormatTimeAgo() for timestamps
- Include proper flag definitions with descriptions matching SDK field comments
- Add help text for commands matching SDK method comments
- Handle errors appropriately
- Match the style of existing commands
# Constraints
- ALWAYS update the SDK version in go.mod - this is the primary purpose
- ALWAYS perform the full enumeration (Steps 2-5) - this is critical for finding gaps
- ALL SDK methods in api.md MUST have corresponding CLI commands, EXCEPT those marked with x-cli-skip in openapi.yaml or noted as internal-only in the mapping guide above
- SKIP endpoints marked with x-cli-skip: true in openapi.yaml - these are internal endpoints not suitable for CLI
- The API spec is taken from the API's main branch at release time and can be ahead of the released SDK. The SDK defines what exists: never add commands or flags for operations or fields this SDK version does not have. Use the spec only for the meaning of things the SDK has.
- Streaming methods may have different CLI implementations (e.g., build events are streamed internally)
- Even if no coverage gaps are found, still leave the SDK version bump and write the coverage report
- Ensure code compiles before finishing
"
- name: Stop model credentials
if: always()
uses: kernel/security-workflows/.github/actions/llm-credentials@4144994f1d6977e07ef5a5832599ee4faaae9550 # main
with:
stop: true
- name: Snapshot publishable changes
id: snapshot
working-directory: /tmp/hypeman-cli
env:
AGENT_BASE: ${{ steps.cli-clone.outputs.agent_base }}
run: |
git reset -q --soft "$AGENT_BASE"
git add -A
git diff --cached --name-only --no-renames -z \
| bash "$GITHUB_WORKSPACE/updater/.github/scripts/cli-update-disallowed-paths.sh" > "$RUNNER_TEMP/excluded-paths"
if [[ -s "$RUNNER_TEMP/excluded-paths" ]]; then
echo "::warning::Discarding changes outside the CLI updater allowlist:"
tr '\0' '\n' < "$RUNNER_TEMP/excluded-paths"
GIT_LITERAL_PATHSPECS=1 git reset -q --pathspec-from-file="$RUNNER_TEMP/excluded-paths" --pathspec-file-nul
fi
git diff --cached --binary --no-renames --no-ext-diff --no-textconv > "$RUNNER_TEMP/changes.patch"
git reset -q --hard
git clean -fdq
if [[ -s "$RUNNER_TEMP/changes.patch" ]]; then
git apply --index "$RUNNER_TEMP/changes.patch"
echo "changed=true" >> "$GITHUB_OUTPUT"
fi
- name: Verify CLI changes
working-directory: /tmp/hypeman-cli
env:
SDK_MODULE: ${{ needs.prefetch.outputs.module }}
SDK_VERSION: ${{ needs.prefetch.outputs.version }}
run: |
test -s "$RUNNER_TEMP/cli-coverage-report.md"
expected=$(go list -m -f '{{.Version}}' "$SDK_MODULE@$SDK_VERSION")
test "$(go list -m -f '{{.Version}}' "$SDK_MODULE")" = "$expected"
# Validation must build against published modules, not local replacements.
test -z "$(go list -m -f '{{with .Replace}}{{.Path}}{{end}}' "$SDK_MODULE")"
go mod edit -json | jq -e '[.Replace[]? | select(.New.Version == null)] | length == 0'
go build ./...
go vet ./...
go test ./...
git diff --cached --check
- name: Upload validated changes
if: steps.snapshot.outputs.changed == 'true'
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: cli-coverage-update
path: |
${{ runner.temp }}/changes.patch
${{ runner.temp }}/cli-coverage-report.md
if-no-files-found: error
retention-days: 7
publish:
needs: [prefetch, generate]
if: needs.generate.outputs.changed == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
env:
SDK_VERSION: ${{ needs.prefetch.outputs.version }}
MAIN_SHA: ${{ needs.generate.outputs.main_sha }}
UPDATE_SHA: ${{ needs.generate.outputs.update_sha }}
REBASED: ${{ needs.generate.outputs.rebased }}
AGENT_BASE_TREE: ${{ needs.generate.outputs.agent_base_tree }}
steps:
- name: Checkout updater scripts
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
persist-credentials: false
- name: Download validated changes
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
name: cli-coverage-update
path: ${{ runner.temp }}/cli-coverage-update
- name: Checkout CLI
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
repository: kernel/hypeman-cli
path: cli
fetch-depth: 0
persist-credentials: false
- name: Apply validated changes
working-directory: cli
run: |
git config user.name "kernel-internal[bot]"
git config user.email "260533166+kernel-internal[bot]@users.noreply.github.com"
if [[ "$REBASED" == true ]]; then
git switch -c cli-coverage-update "$UPDATE_SHA"
git rebase -q "$MAIN_SHA"
else
git switch -c cli-coverage-update "$MAIN_SHA"
fi
if [[ "$(git rev-parse 'HEAD^{tree}')" != "$AGENT_BASE_TREE" ]]; then
echo "::error::The rebuilt update branch differs from the one the changes were validated on."
exit 1
fi
git apply --index "$RUNNER_TEMP/cli-coverage-update/changes.patch"
git diff --cached --name-only --no-renames -z \
| bash ../.github/scripts/cli-update-disallowed-paths.sh > "$RUNNER_TEMP/disallowed-paths"
if [[ -s "$RUNNER_TEMP/disallowed-paths" ]]; then
echo "::error::Validated changes include paths outside the CLI updater allowlist:"
tr '\0' '\n' < "$RUNNER_TEMP/disallowed-paths"
exit 1
fi
if git diff --cached --raw --no-renames | awk '$2 !~ /^(100644|100755|000000)$/ { found = 1 } END { exit !found }'; then
echo "::error::Validated changes may only contain regular files."
exit 1
fi
git commit -q -m "Update Hypeman Go SDK to $SDK_VERSION and CLI coverage"
- name: Generate CLI publishing token
id: publish-token
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ secrets.ADMIN_APP_ID }}
private-key: ${{ secrets.ADMIN_APP_PRIVATE_KEY }}
owner: kernel
repositories: hypeman-cli
permission-contents: write
permission-pull-requests: write
- name: Open or update CLI coverage PR
working-directory: cli
env:
GH_TOKEN: ${{ steps.publish-token.outputs.token }}
run: |
if git diff --quiet origin/main HEAD; then
echo "CLI coverage is already up to date."
exit 0
fi
gh auth setup-git
git push --force-with-lease="cli-coverage-update:$UPDATE_SHA" origin cli-coverage-update
body="$RUNNER_TEMP/cli-coverage-pr.md"
{
printf '## Summary\n\n- Update the Hypeman Go SDK to `%s`.\n' "$SDK_VERSION"
printf '\n## Coverage\n\n'
head -c 60000 "$RUNNER_TEMP/cli-coverage-update/cli-coverage-report.md"
printf '\n## Validation\n\n- Go build\n- Go vet\n- Go tests\n'
} > "$body"
pr=$(gh pr list --repo kernel/hypeman-cli --head cli-coverage-update --base main \
--state open --json number --jq '.[0].number // empty')
title="CLI: Update Hypeman Go SDK to $SDK_VERSION"
if [[ -n "$pr" ]]; then
gh pr edit "$pr" --repo kernel/hypeman-cli --title "$title" --body-file "$body"
else
gh pr create --repo kernel/hypeman-cli --head cli-coverage-update --base main \
--title "$title" --body-file "$body"
fi