chore(devtools): introduce the Onyx Developer Script, ods (#6559)

This commit is contained in:
Jamison Lahman
2025-12-03 15:45:09 -08:00
committed by GitHub
parent 2a40ceab26
commit 828036ceb8
14 changed files with 587 additions and 0 deletions

40
.github/workflows/release-devtools.yml vendored Normal file
View File

@@ -0,0 +1,40 @@
name: Release Devtools
on:
push:
tags:
- "ods/v*.*.*"
jobs:
pypi:
runs-on: ubuntu-latest
environment:
name: release-devtools
permissions:
id-token: write
timeout-minutes: 10
strategy:
matrix:
os-arch:
- {goos: "linux", goarch: "amd64"}
- {goos: "linux", goarch: "arm64"}
- {goos: "windows", goarch: "amd64"}
- {goos: "windows", goarch: "arm64"}
- {goos: "darwin", goarch: "amd64"}
- {goos: "darwin", goarch: "arm64"}
- {goos: "", goarch: ""}
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # ratchet:actions/checkout@v6
with:
persist-credentials: false
fetch-depth: 0
- uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # ratchet:astral-sh/setup-uv@v7
with:
enable-cache: false
- run: |
GOOS="${{ matrix.os-arch.goos }}" \
GOARCH="${{ matrix.os-arch.goarch }}" \
uv build --wheel
working-directory: tools/ods
- run: uv publish
working-directory: tools/ods

View File

@@ -52,6 +52,12 @@ repos:
- id: autoflake
args: [ '--remove-all-unused-imports', '--remove-unused-variables', '--in-place' , '--recursive']
- repo: https://github.com/golangci/golangci-lint
rev: v2.6.2
hooks:
- id: golangci-lint
entry: bash -c "find tools/ -name go.mod -print0 | xargs -0 -I{} bash -c 'cd \"$(dirname {})\" && golangci-lint run ./...'"
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.11.4

View File

@@ -250,6 +250,7 @@ numpy==1.26.4
# pandas-stubs
# shapely
# voyageai
onyx-devtools==0.0.1
openai==2.6.1
# via
# litellm

View File

@@ -175,6 +175,7 @@ dev = [
"ipykernel==6.29.5",
"release-tag==0.4.3",
"zizmor==1.18.0",
"onyx-devtools==0.0.1",
]
# Enterprise Edition features

3
tools/ods/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
ods
__pycache__
*.dist-info/

89
tools/ods/README.md Normal file
View File

@@ -0,0 +1,89 @@
# Onyx Developer Script
[![Deploy Status](https://github.com/onyx-dot-app/onyx/actions/workflows/release-devtools.yml/badge.svg)](https://github.com/onyx-dot-app/onyx/actions/workflows/release-devtools.yml)
[![PyPI](https://img.shields.io/pypi/v/onyx-devtools.svg)](https://pypi.org/project/onyx-devtools/)
`ods` is [onyx.app](https://github.com/onyx-dot-app/onyx)'s devtools utility script.
It is packaged as a python [wheel](https://packaging.python.org/en/latest/discussions/package-formats/) and available from [PyPI](https://pypi.org/project/onyx-devtools/).
## Installation
A stable version of `ods` is provided in the default [python venv](https://github.com/onyx-dot-app/onyx/blob/main/CONTRIBUTING.md#backend-python-requirements)
which is synced automatically if you have [pre-commit](https://github.com/onyx-dot-app/onyx/blob/main/CONTRIBUTING.md#formatting-and-linting)
hooks installed.
While inside the Onyx repository, activate the root project's venv,
```shell
source .venv/bin/activate
```
If you prefer to use the latest version of `ods` and _not_ the stable version in the `pyproject.toml`,
```shell
uvx --from onyx-devtools ods
```
### Autocomplete
`ods` provides autocomplete for `bash`, `fish`, `powershell` and `zsh` shells.
For more information, see `ods completion <shell> --help` for your respective `<shell>`.
#### zsh
*Linux*
```shell
ods completion zsh | sudo tee "${fpath[1]}/_ods" > /dev/null
```
*macOS*
```shell
ods completion zsh > $(brew --prefix)/share/zsh/site-functions/_ods
```
#### bash
```shell
ods completion bash | sudo tee /etc/bash_completion.d/ods > /dev/null
```
_Note: bash completion requires the [bash-completion](https://github.com/scop/bash-completion/) package be installed._
## Upgrading
To upgrade the stable version in the `pyproject.toml`,
```shell
uv add --dev onyx-devtools --upgrade-package onyx-devtools
```
## Building from source
Generally, `go build .` or `go install .` are sufficient.
To build the wheel,
```shell
uv build --wheel
```
To build and install the wheel,
```shell
uv pip install .
```
## Deploy
Releases are deployed automatically when git tags prefaced with `ods/` are pushed to [GitHub](https://github.com/onyx-dot-app/onyx/tags).
The [release-tag](https://pypi.org/project/release-tag/) package can be used to calculate and push the next tag automatically,
```shell
tag --prefix ods
```
See also, [`.github/workflows/release-devtools.yml`](https://github.com/onyx-dot-app/onyx/blob/main/.github/workflows/release-devtools.yml).

View File

@@ -0,0 +1,237 @@
package cmd
import (
"fmt"
"os"
"os/exec"
"regexp"
"strings"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
// CherryPickOptions holds options for the cherry-pick command
type CherryPickOptions struct {
Releases []string
}
// NewCherryPickCommand creates a new cherry-pick command
func NewCherryPickCommand() *cobra.Command {
opts := &CherryPickOptions{}
cmd := &cobra.Command{
Use: "cherry-pick <commit-sha>",
Short: "Cherry-pick a commit to a release branch",
Long: `Cherry-pick a commit to a release branch and create a PR.
This command will:
1. Find the nearest stable version tag (v*.*.* if --release not specified)
2. Fetch the corresponding release branch (release/vMAJOR.MINOR)
3. Create a hotfix branch with the cherry-picked commit
4. Push and create a PR using the GitHub CLI
5. Switch back to the original branch
The --release flag can be specified multiple times to cherry-pick to multiple release branches.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
runCherryPick(cmd, args, opts)
},
}
cmd.Flags().StringSliceVar(&opts.Releases, "release", []string{}, "Release version(s) to cherry-pick to (e.g., 1.0, v1.1). 'v' prefix is optional. Can be specified multiple times.")
return cmd
}
func runCherryPick(cmd *cobra.Command, args []string, opts *CherryPickOptions) {
commitSHA := args[0]
log.Debugf("Cherry-picking commit: %s", commitSHA)
// Save the current branch to switch back later
originalBranch, err := getCurrentBranch()
if err != nil {
log.Fatalf("Failed to get current branch: %v", err)
}
log.Debugf("Original branch: %s", originalBranch)
// Get the short SHA for branch naming
shortSHA := commitSHA
if len(shortSHA) > 8 {
shortSHA = shortSHA[:8]
}
// Determine which releases to target
var releases []string
if len(opts.Releases) > 0 {
// Normalize versions to ensure they have 'v' prefix
for _, rel := range opts.Releases {
releases = append(releases, normalizeVersion(rel))
}
log.Infof("Using specified release versions: %v", releases)
} else {
// Find the nearest stable tag
version, err := findNearestStableTag(commitSHA)
if err != nil {
log.Fatalf("Failed to find nearest stable tag: %v", err)
}
releases = []string{version}
log.Infof("Auto-detected release version: %s", version)
}
// Get commit message for PR title
commitMsg, err := getCommitMessage(commitSHA)
if err != nil {
log.Warnf("Failed to get commit message, using default title: %v", err)
commitMsg = fmt.Sprintf("Hotfix: cherry-pick %s", shortSHA)
}
// Process each release
prURLs := []string{}
for _, release := range releases {
log.Infof("\n--- Processing release %s ---", release)
prURL, err := cherryPickToRelease(commitSHA, shortSHA, release, commitMsg)
if err != nil {
// Switch back to original branch before exiting on error
if checkoutErr := runGitCommand("checkout", originalBranch); checkoutErr != nil {
log.Warnf("Failed to switch back to original branch: %v", checkoutErr)
}
log.Fatalf("Failed to cherry-pick to release %s: %v", release, err)
}
prURLs = append(prURLs, prURL)
}
// Switch back to the original branch
log.Infof("\nSwitching back to original branch: %s", originalBranch)
if err := runGitCommand("checkout", originalBranch); err != nil {
log.Warnf("Failed to switch back to original branch: %v", err)
}
// Print all PR URLs
log.Info("\n=== Summary ===")
for i, prURL := range prURLs {
log.Infof("PR %d: %s", i+1, prURL)
}
}
// cherryPickToRelease cherry-picks a commit to a specific release branch
func cherryPickToRelease(commitSHA, shortSHA, version, commitMsg string) (string, error) {
releaseBranch := fmt.Sprintf("release/%s", version)
hotfixBranch := fmt.Sprintf("hotfix/%s-%s", shortSHA, version)
// Fetch the release branch
log.Infof("Fetching release branch: %s", releaseBranch)
if err := runGitCommand("fetch", "origin", releaseBranch); err != nil {
return "", fmt.Errorf("failed to fetch release branch %s: %w", releaseBranch, err)
}
// Create the hotfix branch from the release branch
log.Infof("Creating hotfix branch: %s", hotfixBranch)
if err := runGitCommand("checkout", "-b", hotfixBranch, fmt.Sprintf("origin/%s", releaseBranch)); err != nil {
return "", fmt.Errorf("failed to create hotfix branch: %w", err)
}
// Cherry-pick the commit
log.Infof("Cherry-picking commit: %s", commitSHA)
if err := runGitCommand("cherry-pick", commitSHA); err != nil {
return "", fmt.Errorf("failed to cherry-pick commit: %w", err)
}
// Push the hotfix branch
log.Infof("Pushing hotfix branch: %s", hotfixBranch)
if err := runGitCommand("push", "-u", "origin", hotfixBranch); err != nil {
return "", fmt.Errorf("failed to push hotfix branch: %w", err)
}
// Create PR using GitHub CLI
log.Info("Creating PR...")
prURL, err := createPR(hotfixBranch, releaseBranch, commitMsg, commitSHA)
if err != nil {
return "", fmt.Errorf("failed to create PR: %w", err)
}
log.Infof("PR created successfully: %s", prURL)
return prURL, nil
}
// getCurrentBranch returns the name of the current git branch
func getCurrentBranch() (string, error) {
cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("git rev-parse failed: %w", err)
}
return strings.TrimSpace(string(output)), nil
}
// normalizeVersion ensures the version has a 'v' prefix
func normalizeVersion(version string) string {
if !strings.HasPrefix(version, "v") {
return "v" + version
}
return version
}
// findNearestStableTag finds the nearest tag matching v*.*.* pattern and returns major.minor
func findNearestStableTag(commitSHA string) (string, error) {
// Get tags that are ancestors of the commit, sorted by version
cmd := exec.Command("git", "describe", "--tags", "--abbrev=0", "--match", "v*.*.*", commitSHA)
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("git describe failed: %w", err)
}
tag := strings.TrimSpace(string(output))
log.Debugf("Found tag: %s", tag)
// Extract major.minor with v prefix from tag (e.g., v1.2.3 -> v1.2)
re := regexp.MustCompile(`^(v\d+\.\d+)\.\d+`)
matches := re.FindStringSubmatch(tag)
if len(matches) < 2 {
return "", fmt.Errorf("tag %s does not match expected format v*.*.* ", tag)
}
return matches[1], nil
}
// runGitCommand executes a git command and returns any error
func runGitCommand(args ...string) error {
log.Debugf("Running: git %s", strings.Join(args, " "))
cmd := exec.Command("git", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// getCommitMessage gets the first line of a commit message
func getCommitMessage(commitSHA string) (string, error) {
cmd := exec.Command("git", "log", "-1", "--format=%s", commitSHA)
output, err := cmd.Output()
if err != nil {
return "", err
}
return strings.TrimSpace(string(output)), nil
}
// createPR creates a pull request using the GitHub CLI
func createPR(headBranch, baseBranch, title, commitSHA string) (string, error) {
body := fmt.Sprintf("Cherry-pick of commit %s to %s branch.", commitSHA, baseBranch)
cmd := exec.Command("gh", "pr", "create",
"--base", baseBranch,
"--head", headBranch,
"--title", title,
"--body", body,
)
output, err := cmd.Output()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
return "", fmt.Errorf("%w: %s", err, string(exitErr.Stderr))
}
return "", err
}
prURL := strings.TrimSpace(string(output))
return prURL, nil
}

48
tools/ods/cmd/root.go Normal file
View File

@@ -0,0 +1,48 @@
package cmd
import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
Version string
Commit string
)
// RootOptions holds options for the root command
type RootOptions struct {
Debug bool
}
// NewRootCommand creates the root command
func NewRootCommand() *cobra.Command {
opts := &RootOptions{}
cmd := &cobra.Command{
Use: "ods ",
Short: "Developer utilities for working on onyx.app",
Run: rootCmd,
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if opts.Debug {
log.SetLevel(log.DebugLevel)
} else {
log.SetLevel(log.InfoLevel)
}
},
Version: fmt.Sprintf("%s\ncommit %s", Version, Commit),
}
cmd.PersistentFlags().BoolVar(&opts.Debug, "debug", false, "run in debug mode")
// Add subcommands
cmd.AddCommand(NewCherryPickCommand())
return cmd
}
func rootCmd(cmd *cobra.Command, args []string) {
log.Debug("Debug log in rootCmd")
}

14
tools/ods/go.mod Normal file
View File

@@ -0,0 +1,14 @@
module github.com/onyx-dot-app/onyx/tools/ods
go 1.25.0
require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.10.1
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
)

24
tools/ods/go.sum Normal file
View File

@@ -0,0 +1,24 @@
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

44
tools/ods/hatch_build.py Normal file
View File

@@ -0,0 +1,44 @@
from __future__ import annotations
import os
import subprocess
from typing import Any
import manygo
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
class CustomBuildHook(BuildHookInterface):
"""Build hook to compile the Go binary and include it in the wheel."""
def initialize(self, version: Any, build_data: Any) -> None: # noqa: ARG002
"""Build the Go binary before packaging."""
build_data["pure_python"] = False
# Set platform tag for cross-compilation
goos = os.getenv("GOOS")
goarch = os.getenv("GOARCH")
if goos and goarch:
build_data["tag"] = "py3-none-" + manygo.get_platform_tag(
goos=goos, goarch=goarch
)
# Get config and environment
binary_name = self.config["binary_name"]
tag = os.getenv("GITHUB_REF_NAME", "dev").removeprefix(f"{binary_name}/")
commit = os.getenv("GITHUB_SHA", "none")
# Build the Go binary if it doesn't exist
if not os.path.exists(binary_name):
print(f"Building Go binary '{binary_name}'...")
subprocess.check_call( # noqa: S603
[
"go",
"build",
f"-ldflags=-X main.version={tag} -X main.commit={commit} -s -w",
"-o",
binary_name,
],
)
build_data["shared_scripts"] = {binary_name: binary_name}

26
tools/ods/main.go Normal file
View File

@@ -0,0 +1,26 @@
package main
import (
"fmt"
"os"
"github.com/onyx-dot-app/onyx/tools/ods/cmd"
)
var (
version = "dev"
commit = "none"
)
func main() {
// Set the version in the cmd package
cmd.Version = version
cmd.Commit = commit
rootCmd := cmd.NewRootCommand()
if err := rootCmd.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(2)
}
}

38
tools/ods/pyproject.toml Normal file
View File

@@ -0,0 +1,38 @@
[build-system]
requires = ["hatchling", "hatch-vcs", "go-bin~=1.25.0", "manygo"]
build-backend = "hatchling.build"
[project]
name = "onyx-devtools"
description = "Developer utilities for working on onyx.app"
license = {file = "../../LICENSE"}
authors = [{ name = "Onyx AI", email = "founders@onyx.app" }]
readme = "README.md"
requires-python = ">=3.9"
keywords = [
"onyx", "cli", "devtools", "tools", "tooling",
]
classifiers = [
"Programming Language :: Go",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
dynamic = ["version"]
[project.urls]
Repository = "https://github.com/onyx-dot-app/onyx"
[tool.hatch.build]
include = ["go.mod", "go.sum", "main.go", "**/*.go"]
[tool.hatch.version]
source = "vcs"
[tool.hatch.version.raw-options]
root = "../.."
tag_regex = "^ods/v(?P<version>[vV]?\\d+(?:\\.\\d+){0,2}[^\\+]*)(?:\\+.*)?$"
[tool.hatch.build.targets.wheel.hooks.custom]
path = "hatch_build.py"
binary_name = "ods"

16
uv.lock generated
View File

@@ -3515,6 +3515,7 @@ dev = [
{ name = "ipykernel" },
{ name = "mypy" },
{ name = "mypy-extensions" },
{ name = "onyx-devtools" },
{ name = "pandas-stubs" },
{ name = "pre-commit" },
{ name = "pytest" },
@@ -3679,6 +3680,7 @@ dev = [
{ name = "ipykernel", specifier = "==6.29.5" },
{ name = "mypy", specifier = "==1.13.0" },
{ name = "mypy-extensions", specifier = "==1.0.0" },
{ name = "onyx-devtools", specifier = "==0.0.1" },
{ name = "pandas-stubs", specifier = "==2.2.3.241009" },
{ name = "pre-commit", specifier = "==3.2.2" },
{ name = "pytest", specifier = "==8.3.5" },
@@ -3718,6 +3720,20 @@ model-server = [
{ name = "transformers", specifier = "==4.53.0" },
]
[[package]]
name = "onyx-devtools"
version = "0.0.1"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8c/79/9728744a291c4caa1bec61da9539436970a800885f0770cd21a8d72e622c/onyx_devtools-0.0.1-py3-none-any.whl", hash = "sha256:a5037608d9d1b3414ea99b23d5ae8af947be1545be72dc488a95d47fb735ea19", size = 1151800, upload-time = "2025-12-03T17:49:26.068Z" },
{ url = "https://files.pythonhosted.org/packages/08/af/38ca64d1de7907837b36cb25025be2ede292a83be9c2da2b97dcc233e368/onyx_devtools-0.0.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:380223b08d24b2d9664a68029ef0b10286df522be49b967b49a2b30c68406b2e", size = 1140236, upload-time = "2025-12-03T17:49:26.396Z" },
{ url = "https://files.pythonhosted.org/packages/28/72/b757608643c28479e156928f6457b4d3a922c6830ea07929f5324a55f71d/onyx_devtools-0.0.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b9b1e437c1271ede1163cb9369fb578cafa7a3205e46275dfa8c1430882492ad", size = 1074672, upload-time = "2025-12-03T17:49:22.374Z" },
{ url = "https://files.pythonhosted.org/packages/43/63/73d59ae0ae6ef5a8421550c11373a4afa9f3fa38a9863fb93cda059fc692/onyx_devtools-0.0.1-py3-none-manylinux_2_17_aarch64.whl", hash = "sha256:2ad93ff1608218c8b0a0ffadd9ee22784fe1a69a78e2d94155cb64f6755fa9c4", size = 1053583, upload-time = "2025-12-03T17:49:37.188Z" },
{ url = "https://files.pythonhosted.org/packages/0d/fe/4b34cb1976c3f491fc19574534a0a3132fe9c19af82589893d56d4ecfbf7/onyx_devtools-0.0.1-py3-none-manylinux_2_17_x86_64.whl", hash = "sha256:40848106e83d784ec2c7fcc63c5feb69c70d65267bc14b02c6a9580a7d08194a", size = 1151819, upload-time = "2025-12-03T17:49:24.827Z" },
{ url = "https://files.pythonhosted.org/packages/2c/90/af1e6726668cfa6dc93d4c2920f844a16c27431c6bf77b1491c4dbf88d32/onyx_devtools-0.0.1-py3-none-win_amd64.whl", hash = "sha256:b687fd32e8ef9d0ab32a37077530d57faa74cd4df9afe93ff760135deb12edd6", size = 1233570, upload-time = "2025-12-03T17:49:27.118Z" },
{ url = "https://files.pythonhosted.org/packages/8a/dc/58ab74b1e8835461a6eaef1ba72d36de69092ec8bef84858435f55e771e5/onyx_devtools-0.0.1-py3-none-win_arm64.whl", hash = "sha256:09399828c31027f8ada21e531d6b2206aa9b48615863470b0f38ba9bbd358804", size = 1116446, upload-time = "2025-12-03T17:49:23.64Z" },
]
[[package]]
name = "openai"
version = "2.6.1"