Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
fe4e5e8
Fix import progress streaming and pass names on single-template impor…
ptone Jun 18, 2026
62d6274
Warn when server binary is built without web assets (#445)
ptone Jun 18, 2026
9e59488
Sync built image reference back to Hub after scion build (#444)
ptone Jun 18, 2026
cc7645a
Tighten web channel default to actual web clients only (#448)
ptone Jun 18, 2026
71749c1
fix(image-build): use default builder for local docker builds (#442)
subspacecoms Jun 18, 2026
c54f541
build(deps): bump astro from 6.3.2 to 6.4.8 in /docs-site (#438)
dependabot[bot] Jun 18, 2026
0a50cd1
build(deps): bump dompurify from 3.4.9 to 3.4.11 in /web (#439)
dependabot[bot] Jun 18, 2026
f742260
build(deps): bump js-yaml from 4.1.1 to 4.2.0 in /docs-site (#440)
dependabot[bot] Jun 18, 2026
cf14257
build(deps): bump github.com/rclone/rclone from 1.73.5 to 1.74.3 (#441)
dependabot[bot] Jun 18, 2026
3fa3291
Scion/harness journey p1 (#447)
ptone Jun 18, 2026
6d52d6a
fix: pass Hub-hydrated harness-config path to harness.Resolve (#450)
ptone Jun 19, 2026
4bea964
feat(web): harness-config detail delete, image section, and agent log…
ptone Jun 19, 2026
c7a6728
fix(hub): make skill version publish idempotent for draft versions (#…
ptone Jun 19, 2026
f46e291
feat(antigravity): use official install script for CLI installation (…
ptone Jun 20, 2026
8456274
feat(config): add name field to harness-config config.yaml (#456)
ptone Jun 20, 2026
1761f4c
fix(antigravity): use TARGETARCH build arg for multi-platform support…
ptone Jun 20, 2026
c0a1094
Scion/skill create ux p1 (#455)
ptone Jun 21, 2026
8188237
Metrics dashboard improvements (#458)
ptone Jun 21, 2026
a1c6a1e
feat(web): add Capture Auth button to agent detail page (#459)
ptone Jun 21, 2026
81c90c1
feat(antigravity): remove gnome-keyring, switch to file-based OAuth a…
ptone Jun 21, 2026
a6b4cbf
feat(antigravity): restore gnome-keyring to provision flow (#461)
ptone Jun 21, 2026
b62cef1
harness: skills: hand tune team-builder skill
ptone Jun 21, 2026
ac96d57
fix(antigravity): patch gcp settings block for oauth-token agents wit…
ptone Jun 22, 2026
6b412ef
fix(build): use config.yaml image field to determine output image nam…
ptone Jun 22, 2026
773e922
fix(build): use config.yaml image field to determine output image nam…
ptone Jun 22, 2026
aa8bbf5
fix(antigravity): read AGY_TOKEN from bind-mounted target path (#465)
ptone Jun 22, 2026
f2709db
Demonstration of agent-registry with lifecycle hooks
ptone Jun 22, 2026
d286d4c
fix(build): use config.yaml image field to determine output image nam…
ptone Jun 22, 2026
e90707e
fix(antigravity): read AGY_TOKEN from bind-mounted target path (#469)
ptone Jun 22, 2026
7a3a24b
fix(antigravity): accept nested AGY token format with auth_method env…
ptone Jun 22, 2026
4ce9921
skill-bank M5: address Gemini code review findings (#470)
ptone Jun 22, 2026
0b470ab
fix(antigravity): capture token from gnome-keyring when file not writ…
ptone Jun 22, 2026
1c3671a
Use backend instead of signed URLs for skill upload (#474)
ptone Jun 22, 2026
55d739e
fix(antigravity): deduplicate capture-auth entries and treat "already…
ptone Jun 22, 2026
225b036
fixes to metrics reporting
ptone Jun 23, 2026
90d7e3f
fix(capture-auth): treat 'already exists' as successful capture
Jun 23, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions cmd/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,16 @@ field is updated to reference the built image.`,
return fmt.Errorf("no container runtime found (tried docker, podman)")
}

outputImage := harnessConfigName + ":" + tag
imageBaseName := harnessConfigName
if hcDir.Config.Image != "" {
name := hcDir.Config.Image
if colonIdx := strings.LastIndex(name, ":"); colonIdx >= 0 {
name = name[:colonIdx]
}
imageBaseName = name
}
Comment on lines +93 to +100

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the image name contains a registry port but no tag (e.g., localhost:5001/my-image), strings.LastIndex(name, ":") will match the port's colon instead of a tag separator. This results in the image name being incorrectly truncated to just the registry host (e.g., localhost).

To prevent this, ensure that the matched colon appears after the last slash / in the image path.

Suggested change
imageBaseName := harnessConfigName
if hcDir.Config.Image != "" {
name := hcDir.Config.Image
if colonIdx := strings.LastIndex(name, ":"); colonIdx >= 0 {
name = name[:colonIdx]
}
imageBaseName = name
}
imageBaseName := harnessConfigName
if hcDir.Config.Image != "" {
name := hcDir.Config.Image
if colonIdx := strings.LastIndex(name, ":"); colonIdx >= 0 {
if slashIdx := strings.LastIndex(name, "/"); slashIdx < colonIdx {
name = name[:colonIdx]
}
}
imageBaseName = name
}


outputImage := imageBaseName + ":" + tag
if buildPush {
imageRegistry := ""
if settings != nil {
Expand All @@ -99,7 +108,7 @@ field is updated to reference the built image.`,
if imageRegistry == "" {
return fmt.Errorf("--push requires image_registry to be configured")
}
outputImage = imageRegistry + "/" + harnessConfigName + ":" + tag
outputImage = imageRegistry + "/" + imageBaseName + ":" + tag
}

buildArgs := []string{"build",
Expand Down Expand Up @@ -167,6 +176,26 @@ field is updated to reference the built image.`,
}
fmt.Printf("Updated %s image to %s\n", configPath, outputImage)

// Sync updated config to Hub so agents pick up the new image.
var gp string
if projectPath != "" {
if resolved, err := config.GetResolvedProjectDir(projectPath); err == nil {
gp = resolved
}
} else if resolved, err := config.GetResolvedProjectDir(""); err == nil {
gp = resolved
}
hubCtx, hubErr := CheckHubAvailabilityWithOptions(gp, true)
if hubErr != nil {
fmt.Printf("Warning: could not sync to Hub: %v\n", hubErr)
fmt.Println("Run 'scion harness-config push " + harnessConfigName + "' to sync manually.")
} else if hubCtx != nil {
if err := syncHarnessConfigToHub(hubCtx, harnessConfigName, hcDir.Path, "global", "", hcDir.Config.Harness); err != nil {
fmt.Printf("Warning: failed to sync to Hub: %v\n", err)
fmt.Println("Run 'scion harness-config push " + harnessConfigName + "' to sync manually.")
}
}

return nil
},
}
Expand Down
4 changes: 4 additions & 0 deletions cmd/harness_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,9 @@ var harnessConfigShowCmd = &cobra.Command{
fmt.Printf("Content Hash: %s\n", truncateHash(hc.ContentHash))
fmt.Printf("Scope: %s\n", hc.Scope)
fmt.Printf("Files: %d\n", len(hc.Files))
if hc.SourceURL != "" {
fmt.Printf("Source URL: %s\n", hc.SourceURL)
}
return nil
}
}
Expand Down Expand Up @@ -834,6 +837,7 @@ func init() {
harnessConfigCmd.AddCommand(harnessConfigShowCmd)
harnessConfigCmd.AddCommand(harnessConfigDeleteCmd)
harnessConfigCmd.AddCommand(harnessConfigInstallCmd)
harnessConfigCmd.AddCommand(harnessConfigUpdateCmd)

// Flags for list command
harnessConfigListCmd.Flags().Bool("hub", false, "Include Hub results")
Expand Down
12 changes: 9 additions & 3 deletions cmd/harness_config_install.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,16 @@ func runHarnessConfigInstall(cmd *cobra.Command, args []string) error {

name := nameOverride
if name == "" {
name = deriveHarnessConfigName(source)
if hcDir.Config.Name != "" {
name = hcDir.Config.Name
} else if hcDir.Config.Harness != "" {
name = hcDir.Config.Harness
} else {
name = deriveHarnessConfigName(source)
}
}
if name == "" || name == "." || name == "/" {
return fmt.Errorf("could not derive harness-config name from source %q; use --name to specify", source)
if name == "" || name == "." || name == ".." || strings.ContainsAny(name, "/\\") {
return fmt.Errorf("invalid harness-config name %q; name cannot contain path components or separators", name)
}

hubCtx, err := CheckHubAvailabilityWithOptions(gp, true)
Expand Down
197 changes: 197 additions & 0 deletions cmd/harness_config_update.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cmd

import (
"context"
"fmt"
"time"

"github.com/GoogleCloudPlatform/scion/pkg/config"
"github.com/GoogleCloudPlatform/scion/pkg/hubclient"
"github.com/spf13/cobra"
)

var harnessConfigUpdateCmd = &cobra.Command{
Use: "update [name]",
Short: "Re-import a harness-config from its source URL",
Long: `Re-imports a harness-config from its stored source URL, pulling the latest
version from the remote source.

If --url is provided, it overrides (and updates) the stored source URL.
Use --all to re-import all harness-configs that have a stored source URL.

Examples:
scion harness-config update my-claude
scion harness-config update my-claude --url https://github.com/org/repo/tree/main/harness-configs/claude
scion harness-config update --all`,
Args: cobra.MaximumNArgs(1),
RunE: runHarnessConfigUpdate,
}

func runHarnessConfigUpdate(cmd *cobra.Command, args []string) error {
urlOverride, _ := cmd.Flags().GetString("url")
all, _ := cmd.Flags().GetBool("all")

if len(args) == 0 && !all {
return fmt.Errorf("specify a harness-config name or use --all")
}
if all && urlOverride != "" {
return fmt.Errorf("--all and --url cannot be used together")
}

var gp string
if projectPath != "" {
resolved, err := config.GetResolvedProjectDir(projectPath)
if err != nil {
return fmt.Errorf("failed to resolve project path %q: %w", projectPath, err)
}
gp = resolved
} else if projectDir, err := config.GetResolvedProjectDir(""); err == nil {
gp = projectDir
}

hubCtx, err := CheckHubAvailabilityWithOptions(gp, true)
if err != nil {
return err
}
if hubCtx == nil {
return fmt.Errorf("Hub is not available. The update command requires a Hub connection.")

Check failure on line 71 in cmd/harness_config_update.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ST1005: error strings should not end with punctuation or newlines (staticcheck)
}

PrintUsingHub(hubCtx.Endpoint)

ctx, cancel := context.WithTimeout(cmd.Context(), 120*time.Second)
defer cancel()

if all {
return updateAllHarnessConfigs(ctx, hubCtx)
}

name := args[0]
return updateSingleHarnessConfig(ctx, hubCtx, name, urlOverride)
}

func updateSingleHarnessConfig(ctx context.Context, hubCtx *HubContext, name, urlOverride string) error {
resp, err := hubCtx.Client.HarnessConfigs().List(ctx, &hubclient.ListHarnessConfigsOptions{
Name: name,
Status: "active",
})
if err != nil {
return fmt.Errorf("failed to search Hub: %w", err)
}
if resp == nil {
return fmt.Errorf("harness-config %q not found on Hub", name)
}

var match *hubclient.HarnessConfig
for i := range resp.HarnessConfigs {
if resp.HarnessConfigs[i].Name == name || resp.HarnessConfigs[i].Slug == name {
match = &resp.HarnessConfigs[i]
break
}
}
if match == nil {
return fmt.Errorf("harness-config %q not found on Hub", name)
}

sourceURL := urlOverride
if sourceURL == "" {
sourceURL = match.SourceURL
}
if sourceURL == "" {
return fmt.Errorf("harness-config %q has no stored source URL. Use --url to specify one.", name)

Check failure on line 115 in cmd/harness_config_update.go

View workflow job for this annotation

GitHub Actions / golangci-lint

ST1005: error strings should not end with punctuation or newlines (staticcheck)
}

if !isJSONOutput() {
fmt.Printf("Updating %q from %s...\n", name, sourceURL)
}

result, err := hubCtx.Client.HarnessConfigs().Reimport(ctx, match.ID, urlOverride)
if err != nil {
return fmt.Errorf("reimport failed: %w", err)
}
if result == nil {
return fmt.Errorf("reimport returned no result for %q", name)
}

if isJSONOutput() {
return outputJSON(ActionResult{
Status: "success",
Command: "harness-config update",
Message: fmt.Sprintf("Updated %d harness-config(s)", result.Count),
Details: map[string]interface{}{
"name": name,
"imported": result.HarnessConfigs,
"count": result.Count,
},
})
}

fmt.Printf("Updated %d harness-config(s): %v\n", result.Count, result.HarnessConfigs)
return nil
}

func updateAllHarnessConfigs(ctx context.Context, hubCtx *HubContext) error {
resp, err := hubCtx.Client.HarnessConfigs().List(ctx, &hubclient.ListHarnessConfigsOptions{
Status: "active",
})
if err != nil {
return fmt.Errorf("failed to list harness-configs: %w", err)
}
if resp == nil {
return fmt.Errorf("no harness-configs found")
}

var updated, skipped, failed int
jsonOut := isJSONOutput()
for _, hc := range resp.HarnessConfigs {
if hc.SourceURL == "" {
skipped++
continue
}
if !jsonOut {
fmt.Printf("Updating %q from %s...\n", hc.Name, hc.SourceURL)
}
_, err := hubCtx.Client.HarnessConfigs().Reimport(ctx, hc.ID, "")
if err != nil {
if !jsonOut {
fmt.Printf(" Failed: %s\n", err)
}
failed++
continue
}
if !jsonOut {
fmt.Printf(" Done.\n")
}
updated++
}

if jsonOut {
return outputJSON(ActionResult{
Status: "success",
Command: "harness-config update --all",
Message: fmt.Sprintf("Updated %d, skipped %d (no source URL), failed %d", updated, skipped, failed),
})
}

fmt.Printf("\nUpdated %d, skipped %d (no source URL), failed %d\n", updated, skipped, failed)
return nil
}

func init() {
harnessConfigUpdateCmd.Flags().String("url", "", "Override the stored source URL")
harnessConfigUpdateCmd.Flags().Bool("all", false, "Update all harness-configs that have a stored source URL")
}
4 changes: 4 additions & 0 deletions cmd/server_foreground.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import (
"github.com/GoogleCloudPlatform/scion/pkg/store/entadapter"
"github.com/GoogleCloudPlatform/scion/pkg/util"
"github.com/GoogleCloudPlatform/scion/pkg/util/logging"
"github.com/GoogleCloudPlatform/scion/web"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -328,6 +329,9 @@ func runServerStart(cmd *cobra.Command, args []string) error {
hubSrv.StartBackgroundServices(ctx)
}

if !web.AssetsEmbedded && webAssetsDir == "" {
slog.Warn("This binary was built without web assets. The web UI will not be available. Run 'make web' and rebuild to include the web frontend, or use --web-assets-dir.")
}
log.Printf("Starting Web Frontend on %s:%d", cfg.Hub.Host, webPort)
wg.Add(1)
go func() {
Expand Down
37 changes: 24 additions & 13 deletions cmd/skill_registries.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func runRegistriesList(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Hub connection required: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second)
defer cancel()

resp, err := hubCtx.Client.SkillRegistries().List(ctx)
Expand Down Expand Up @@ -83,7 +83,7 @@ func runRegistriesAdd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Hub connection required: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second)
defer cancel()

endpoint, _ := cmd.Flags().GetString("endpoint")
Expand Down Expand Up @@ -133,7 +133,7 @@ func runRegistriesShow(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Hub connection required: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second)
defer cancel()

registry, err := hubCtx.Client.SkillRegistries().Get(ctx, args[0])
Expand Down Expand Up @@ -175,7 +175,7 @@ func runRegistriesUpdate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Hub connection required: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second)
defer cancel()

endpoint, _ := cmd.Flags().GetString("endpoint")
Expand All @@ -185,13 +185,24 @@ func runRegistriesUpdate(cmd *cobra.Command, args []string) error {
authToken, _ := cmd.Flags().GetString("auth-token")
resolvePath, _ := cmd.Flags().GetString("resolve-path")

req := &hubclient.UpdateSkillRegistryRequest{
Endpoint: endpoint,
TrustLevel: trust,
Status: status,
Description: description,
AuthToken: authToken,
ResolvePath: resolvePath,
req := &hubclient.UpdateSkillRegistryRequest{}
if cmd.Flags().Changed("endpoint") {
req.Endpoint = &endpoint
}
if cmd.Flags().Changed("trust") {
req.TrustLevel = &trust
}
if cmd.Flags().Changed("status") {
req.Status = &status
}
if cmd.Flags().Changed("description") {
req.Description = &description
}
if cmd.Flags().Changed("auth-token") {
req.AuthToken = &authToken
}
if cmd.Flags().Changed("resolve-path") {
req.ResolvePath = &resolvePath
}

registry, err := hubCtx.Client.SkillRegistries().Update(ctx, args[0], req)
Expand Down Expand Up @@ -220,7 +231,7 @@ func runRegistriesRemove(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Hub connection required: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second)
defer cancel()

if err := hubCtx.Client.SkillRegistries().Delete(ctx, args[0]); err != nil {
Expand Down Expand Up @@ -248,7 +259,7 @@ func runRegistriesPin(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Hub connection required: %w", err)
}

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
ctx, cancel := context.WithTimeout(cmd.Context(), 30*time.Second)
defer cancel()

hash, _ := cmd.Flags().GetString("hash")
Expand Down
Loading
Loading