From e34572a39f6c8ecf84dbd9b34770f5650852aa06 Mon Sep 17 00:00:00 2001 From: Shiti Saxena Date: Fri, 24 Jul 2026 16:29:26 -0700 Subject: [PATCH 1/2] ci: pipeline to publish client --- .github/workflows/forge-release.yml | 35 +++++++++++++++++++++++++++++ clients/typescript/package.json | 8 +++---- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/.github/workflows/forge-release.yml b/.github/workflows/forge-release.yml index 0428ce6..5d12b87 100644 --- a/.github/workflows/forge-release.yml +++ b/.github/workflows/forge-release.yml @@ -282,3 +282,38 @@ jobs: with: packages-dir: dist repository-url: https://test.pypi.org/legacy/ + + publish-ts-client: + runs-on: ubuntu-latest + needs: [ prepare-release, verify-release ] + if: ${{ needs.prepare-release.outputs.skip_release != 'true' }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ needs.prepare-release.outputs.tag }} + + - name: Set up Node + uses: actions/setup-node@v6 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + package-manager-cache: false + + - name: Install dependencies + working-directory: clients/typescript + run: npm ci + + - name: Stamp package version + working-directory: clients/typescript + run: npm version "${{ needs.prepare-release.outputs.version }}" --no-git-tag-version --allow-same-version + + - name: Build + working-directory: clients/typescript + run: npm run build + + - name: Publish to npm + if: ${{ needs.prepare-release.outputs.publish_target == 'pypi' }} + working-directory: clients/typescript + run: npm publish --access public + diff --git a/clients/typescript/package.json b/clients/typescript/package.json index 97ba859..b90b055 100644 --- a/clients/typescript/package.json +++ b/clients/typescript/package.json @@ -1,14 +1,14 @@ { "name": "@rustic-ai/api-client", - "version": "0.0.9", - "description": "TypeScript client for the Rustic AI Forge HTTP API, generated from its OpenAPI specification.", - "license": "Apache-2.0", + "version": "0.4.3", + "description": "TypeScript client for the Rustic AI HTTP API, generated from its OpenAPI specification.", + "license": "MIT", "repository": { "type": "git", "url": "git+https://github.com/rustic-ai/forge.git", "directory": "clients/typescript" }, - "homepage": "https://github.com/rustic-ai/forge/tree/main/clients/typescript", + "homepage": "https://rustic-ai.github.io/rustic-ai/api/", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ From 5ed059a5f5801bec401e058045069df6bfe3a00d Mon Sep 17 00:00:00 2001 From: Shiti Saxena Date: Fri, 24 Jul 2026 18:15:17 -0700 Subject: [PATCH 2/2] fix: empty response handling in keychain_secret_store --- forge-go/api/secrets.go | 5 +++++ forge-go/secrets/keychain_secret_store.go | 9 +++++++-- forge-go/secrets/keychain_secret_store_test.go | 17 +++++++++++++++++ forge-go/version/version.go | 2 +- forge-python/pyproject.toml | 2 +- forge-python/uv.lock | 2 +- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/forge-go/api/secrets.go b/forge-go/api/secrets.go index 4478297..84d9184 100644 --- a/forge-go/api/secrets.go +++ b/forge-go/api/secrets.go @@ -87,6 +87,11 @@ func (s *Server) handleListSecrets() http.HandlerFunc { ReplyError(w, http.StatusInternalServerError, err.Error()) return } + if names == nil { + // The contract declares "secrets" as a required array: an org with no + // secrets must serialize as [], never null. + names = []string{} + } ReplyJSON(w, http.StatusOK, map[string]interface{}{"secrets": names}) } } diff --git a/forge-go/secrets/keychain_secret_store.go b/forge-go/secrets/keychain_secret_store.go index 1d6ea67..3a50c43 100644 --- a/forge-go/secrets/keychain_secret_store.go +++ b/forge-go/secrets/keychain_secret_store.go @@ -36,8 +36,13 @@ func (s *KeychainSecretStore) Exists(orgID, name string) bool { } func (s *KeychainSecretStore) List(orgID string) ([]string, error) { - var filtered []string availableKeys, err := keyring.ListUsers(s.service) + if err != nil { + return nil, err + } + // Non-nil so an org with no secrets marshals as [] rather than null; the + // contract declares "secrets" as a required array. + filtered := make([]string, 0) // using empty string for name since we want to list all secrets saved for the org prefix := SecretStoreKey(orgID, "") for _, key := range availableKeys { @@ -48,5 +53,5 @@ func (s *KeychainSecretStore) List(orgID string) ([]string, error) { } } sort.Strings(filtered) - return filtered, err + return filtered, nil } diff --git a/forge-go/secrets/keychain_secret_store_test.go b/forge-go/secrets/keychain_secret_store_test.go index a508888..9a7d33a 100644 --- a/forge-go/secrets/keychain_secret_store_test.go +++ b/forge-go/secrets/keychain_secret_store_test.go @@ -48,6 +48,23 @@ func TestKeychainSecretStore_CRUDAndList(t *testing.T) { } } +func TestKeychainSecretStore_ListEmptyIsNonNil(t *testing.T) { + s := newTestKeychainStore(t) + + names, err := s.List("org-with-no-secrets") + if err != nil { + t.Fatalf("List: %v", err) + } + // A nil slice marshals to JSON null, which violates the contract's required + // "secrets" array. + if names == nil { + t.Fatal("expected empty non-nil slice, got nil") + } + if len(names) != 0 { + t.Fatalf("expected no names, got %+v", names) + } +} + func TestKeychainSecretStore_OrgIsolation(t *testing.T) { s := newTestKeychainStore(t) diff --git a/forge-go/version/version.go b/forge-go/version/version.go index b521d4b..684e076 100644 --- a/forge-go/version/version.go +++ b/forge-go/version/version.go @@ -1,7 +1,7 @@ package version var ( - Version = "0.4.3" + Version = "0.4.4" GitCommit = "none" BuildDate = "unknown" ) diff --git a/forge-python/pyproject.toml b/forge-python/pyproject.toml index ff239d3..be1859b 100644 --- a/forge-python/pyproject.toml +++ b/forge-python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "rusticai-forge" -version = "0.4.3" +version = "0.4.4" description = "Python agent wrapper and execution engine for Forge" readme = "README.md" requires-python = ">=3.13,<3.14" diff --git a/forge-python/uv.lock b/forge-python/uv.lock index f8ec821..8a5d618 100644 --- a/forge-python/uv.lock +++ b/forge-python/uv.lock @@ -837,7 +837,7 @@ wheels = [ [[package]] name = "rusticai-forge" -version = "0.4.3" +version = "0.4.4" source = { editable = "." } dependencies = [ { name = "httpx" },