feat: add fullsend admin CLI with install/uninstall/analyze - #142
Conversation
Critical fixes: - Fix XSS vulnerability in app manifest HTML form (html.EscapeString) - Add retry with backoff on rate-limited responses (429 + 403 w/ Retry-After) - Fix exchangeManifestCode to use context-aware HTTP client with timeout - Add GetRepo to forge.Client for O(1) repo existence checks Important fixes: - Add forge.IsNotFound/ErrNotFound for structured error discrimination - Fix runUninstall to return error when uninstall operations fail - Secrets layer Analyze now checks both secrets and variables - Move DefaultAgentRoles to config package to decouple CLI from forge/github - Fix FakeClient embedding by pointer in enrollment test (mutex safety) - Add shared forge.ConfigRepoName constant used across all packages - Fix go.mod to use go 1.25.8 Minor fixes: - Use errors.As in isNotFound instead of manual unwrap loop - Add pagination to ListRepoPullRequests - Delete pointless TestCompileTimeInterfaceCheck - Add bin/ to .gitignore Assisted-by: OpenCode claude-opus-4-6@default
Assisted-by: OpenCode claude-opus-4-6@default
Assisted-by: OpenCode claude-opus-4-6@default
Add the ui.Printer type with lipgloss-styled methods for consistent terminal output across the CLI: banner, headers, step indicators, key-value pairs, summary/error boxes, and PR links. Includes full test coverage for all 12 methods. Assisted-by: OpenCode claude-opus-4-6@default
The forge.Client interface abstracts all git forge operations, enabling future support for GitHub, GitLab, and Forgejo. Assisted-by: OpenCode claude-opus-4-6@default
Implements all forge.Client methods against the GitHub REST API including repo management, file operations, secret encryption, and workflow queries. Includes GitHub-specific types for App configuration with role-based presets. Assisted-by: OpenCode claude-opus-4-6@default
Handles OrgConfig types, YAML marshal/unmarshal, validation, and helper methods for accessing enabled repos and agent slugs. Assisted-by: OpenCode claude-opus-4-6@default
Layers represent discrete installation concerns processed in order for install, reverse order for uninstall, and assessed individually for analyze. Assisted-by: OpenCode claude-opus-4-6@default
Handles creation, configuration, and teardown of the org-level .fullsend configuration repository. The layer creates the repo (private or public based on org capability), writes config.yaml, and provides analysis of existing installation state. Assisted-by: OpenCode claude-opus-4-6@default
Manages reusable agent dispatch workflow, onboarding workflow, and CODEOWNERS in the .fullsend config repo. Assisted-by: OpenCode claude-opus-4-6@default
Stores agent app private keys as repo secrets and app IDs as repo variables in the .fullsend config repo. Assisted-by: OpenCode claude-opus-4-6@default
Creates enrollment PRs with shim workflow files for enabled repos that are not yet connected to the fullsend agent pipeline. Assisted-by: OpenCode claude-opus-4-6@default
Handles creating and installing per-role GitHub Apps using the manifest flow, with support for reusing existing apps. Assisted-by: OpenCode claude-opus-4-6@default
Implements fullsend admin {install,uninstall,analyze} <org> with
layer-based installation model and forge-agnostic client interface.
- Root command with Cobra, version support, and silence flags
- Admin subcommand grouping install, uninstall, and analyze
- Install: app setup, repo discovery, layer stack creation and execution
- Uninstall: confirmation prompt, layer teardown, manual cleanup hints
- Analyze: layer-by-layer status assessment with actionable reporting
- Token resolution from GH_TOKEN, GITHUB_TOKEN, or gh CLI
- Org name validation
- Dry-run mode for install preview
Assisted-by: OpenCode claude-opus-4-6@default
Critical fixes: - Fix XSS vulnerability in app manifest HTML form (html.EscapeString) - Add retry with backoff on rate-limited responses (429 + 403 w/ Retry-After) - Fix exchangeManifestCode to use context-aware HTTP client with timeout - Add GetRepo to forge.Client for O(1) repo existence checks Important fixes: - Add forge.IsNotFound/ErrNotFound for structured error discrimination - Fix runUninstall to return error when uninstall operations fail - Secrets layer Analyze now checks both secrets and variables - Move DefaultAgentRoles to config package to decouple CLI from forge/github - Fix FakeClient embedding by pointer in enrollment test (mutex safety) - Add shared forge.ConfigRepoName constant used across all packages - Fix go.mod to use go 1.25.8 Minor fixes: - Use errors.As in isNotFound instead of manual unwrap loop - Add pagination to ListRepoPullRequests - Delete pointless TestCompileTimeInterfaceCheck - Add bin/ to .gitignore Assisted-by: OpenCode claude-opus-4-6@default
- Resolve merge conflicts with main (Makefile, .gitignore) - Fix detect-private-key pre-commit hook failure by constructing PEM headers at runtime in secrets_test.go - Fix GetFileContent base64 decoding: strip newlines from GitHub API's line-wrapped base64 content before decoding - Fix enrollment layer silently swallowing API errors: now properly distinguishes not-found from other errors in enrollRepo and Analyze - Fix CreateOrUpdateFile: handle non-200/404 GET responses instead of silently proceeding without a SHA - Fix XSS in appsetup callback page: escape creds.Name in HTML output - Add actions/setup-go step to CI workflow so go vet works reliably - Fix trailing whitespace in plan doc Assisted-by: OpenCode claude-opus-4-6@default
c19ff32 to
d77fe3f
Compare
|
Somehow, I totally forgot about the vertex api credentials. We're going to need those. At this point, if you're all okay with the outline of this, I'd prefer to get it merged and follow up with that detail in a patch PR on top. |
Demo / code mismatch, credential scoping, and architectural concerns1. Demo video shows v6 branch behavior, not the merged code The demo video demonstrates
The demo action run at 2. Merged code: PEM stored in The merged // internal/layers/secrets.go
s.client.CreateRepoSecret(ctx, s.org, ".fullsend", sName, agent.PEM)But with secrets:
APP_PRIVATE_KEY: ${{ secrets.FULLSEND_FULLSEND_APP_PRIVATE_KEY }}This expression resolves in Fix: Use 3. v6 architecture: centralizing agent workload in The v6
4. v6 dispatch token (PAT) has lifecycle concerns The v6 branch requires an org admin to manually create a fine-grained PAT (
Alternative worth considering: use the GitHub App installation token (already available from the per-role Apps) to trigger the dispatch, eliminating the need for a stored PAT entirely. |
…n CLI implementation Merges the correct admin CLI implementation from PR #160 after reverting the incorrectly merged PR #142. The v6 branch includes: - Dispatch token layer for org-level cross-repo dispatch - Preflight scope checks and auto-reuse of existing apps - Idempotent layer operations - ADRs for forge abstraction, layer model, app model, dispatch security - GoReleaser release workflow - Numerous bug fixes and improvements Assisted-by: OpenCode claude-opus-4-6@default
…yer) The e2e tests were written against the simpler admin CLI from PR #142 but need to work with the v6 admin CLI from PR #160 which adds: - DispatchTokenLayer for org-level cross-repo dispatch secrets - Preflight scope checks - Additional forge.Client interface methods (org secrets, dispatch) Changes: - Add DispatchTokenLayer to buildTestLayerStack, runUninstall, runUninstallAllowNotFound, and verifyNotInstalled - Add dispatch token verification (OrgSecretExists) to verifyInstalled and verifyNotInstalled - Add E2E_DISPATCH_TOKEN env var for non-interactive dispatch token provisioning - Collect enrolled repo IDs during install for dispatch token scoping - Add cleanup of stale FULLSEND_DISPATCH_TOKEN org secret - Second install uses empty dispatch token to exercise reuse path Assisted-by: OpenCode claude-opus-4-6@default
Critical fixes: - Fix XSS vulnerability in app manifest HTML form (html.EscapeString) - Add retry with backoff on rate-limited responses (429 + 403 w/ Retry-After) - Fix exchangeManifestCode to use context-aware HTTP client with timeout - Add GetRepo to forge.Client for O(1) repo existence checks Important fixes: - Add forge.IsNotFound/ErrNotFound for structured error discrimination - Fix runUninstall to return error when uninstall operations fail - Secrets layer Analyze now checks both secrets and variables - Move DefaultAgentRoles to config package to decouple CLI from forge/github - Fix FakeClient embedding by pointer in enrollment test (mutex safety) - Add shared forge.ConfigRepoName constant used across all packages - Fix go.mod to use go 1.25.8 Minor fixes: - Use errors.As in isNotFound instead of manual unwrap loop - Add pagination to ListRepoPullRequests - Delete pointless TestCompileTimeInterfaceCheck - Add bin/ to .gitignore Assisted-by: OpenCode claude-opus-4-6@default
Summary
Implements the
fullsend adminCLI to deliver the installation and bootstrap workflow described in #124. An org administrator runs a single command to onboard their organization: the CLI creates a.fullsendconfig repo with safe defaults, sets up CI workflows and CODEOWNERS, stores agent app credentials as secrets, and opens enrollment PRs on enabled repos — nothing changes in any repo until those PRs are reviewed and merged.Two architectural choices shape the implementation:
Forge abstraction via interfaces — All git forge operations go through a
forge.Clientinterface, making it straightforward to add GitLab and Forgejo support alongside the current GitHub implementation.Layered install/uninstall/analyze model — Installation is decomposed into ordered layers (config-repo → workflows → secrets → enrollment), each implementing
Install,Uninstall, andAnalyzemethods. This enables theanalyzesubcommand to report exactly what's installed, what's missing, and whatinstallwould do.CLI Structure
Demo
demo-reduced.mp4
Architecture
Forge Abstraction (
internal/forge/)forge.Clientinterface with 17 methods covering repos, files, branches, PRs, secrets, workflows, and app installationsforge.FakeClient— thread-safe test double with error injection and call recordingforge/github.LiveClient— GitHub REST API implementation with NaCl secret encryptionLayer Model (
internal/layers/)Layerinterface:Name(),Install(),Uninstall(),Analyze()Stackprocesses layers in order (install) or reverse order (uninstall).fullsendconfig repositoryOther Packages
internal/config/— OrgConfig types, YAML marshal/unmarshal, validationinternal/appsetup/— GitHub App manifest flow for per-role agent appsinternal/ui/— Styled terminal output via lipglossinternal/cli/— Cobra CLI withadminsubcommand groupTest Coverage
All packages have unit tests:
forge— 100% coverage (fake client with race detector)config— 94.3% coveragelayers— 91.0% coverage (all 5 layer implementations tested)forge/github— 80.5% coverage (httptest-based API tests)appsetup— 41.5% (manifest flow partially tested due to browser interaction)ui— 100% coverageVerification
go test ./... -count=1 -cover -race— all passgo vet ./...— cleango build -o bin/fullsend ./cmd/fullsend/— builds successfully