Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions .github/workflows/forge-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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

8 changes: 4 additions & 4 deletions clients/typescript/package.json
Original file line number Diff line number Diff line change
@@ -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": [
Expand Down
5 changes: 5 additions & 0 deletions forge-go/api/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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})
}
}
Expand Down
9 changes: 7 additions & 2 deletions forge-go/secrets/keychain_secret_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -48,5 +53,5 @@ func (s *KeychainSecretStore) List(orgID string) ([]string, error) {
}
}
sort.Strings(filtered)
return filtered, err
return filtered, nil
}
17 changes: 17 additions & 0 deletions forge-go/secrets/keychain_secret_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion forge-go/version/version.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package version

var (
Version = "0.4.3"
Version = "0.4.4"
GitCommit = "none"
BuildDate = "unknown"
)
2 changes: 1 addition & 1 deletion forge-python/pyproject.toml
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
2 changes: 1 addition & 1 deletion forge-python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading