Skip to content
Open
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,7 @@ jobs/**
node_modules/
bench/__pycache__
.ai/

# Beads / Dolt files (added by bd init)
.dolt/
.beads-credential-key
155 changes: 155 additions & 0 deletions cachyos/PKGBUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Maintainer: CachyOS Custom Overlay <overlay@cachyos.org>
# Contributor: ForgeCode packaging <noreply@forgecode.dev>
#
# PKGBUILD for Forge (https://forgecode.dev) built with CachyOS optimizations.
# Intended for use in a private/custom CachyOS overlay repository served from
# a Proxmox VM (or similar) using devtools + clean chroot (extra-x86_64-build).
#
# CachyOS makepkg.conf (or equivalent) supplies the RUSTFLAGS containing
# -C target-cpu=x86-64-v3 (or v4 for supported CPUs)
# plus any other CachyOS LTO / march / mtune settings.
# This PKGBUILD does *not* override RUSTFLAGS so that the chroot environment
# (and local makepkg.conf on CachyOS workstations) fully controls the opts.
#
# The binary is completely self-contained for the ZSH plugin:
# * shell-plugin/forge.setup.zsh , lib/*.zsh and completions are include_dir!'d
# and include_str!'d at compile time into the executable.
# * Therefore we only ship /usr/bin/forge ; no extra runtime data files required.
# * After package install users run `forge zsh setup` (or the interactive first-run
# banner) to populate the # >>> forge initialize >>> block in their ~/.zshrc.
#
# Compatible with the from-source scripts/install.sh (in the forge repo) for
# local dev/CachyOS workstation testing outside of packaging:
# RUSTFLAGS="-C target-cpu=x86-64-v3" ./scripts/install.sh --reinstall
#
# Versioning strategy for custom/rebase/feature builds:
# - pkgver is the upstream base (0.1.0 here; update when rebasing on tags)
# - pkgrel carries a local suffix (e.g. 1.cachy , 1.xai20260606) so that
# our overlay packages never accidentally satisfy an official repo version
# and we can force upgrades on re-packs.
#
# Build in clean chroot example (from the overlay checkout containing this PKGBUILD):
# extra-x86_64-build # or cachy-x86_64-build if the overlay provides a wrapper
#
# Local (dirty) test build from inside a forge source tree (for quick iteration):
# cd /path/to/forgecode
# cp cachyos/PKGBUILD .
# # (edit pkgrel if desired)
# makepkg -s --nocheck # will use current tree as "source" via the hack below
#
# Co-Authored-By: ForgeCode <noreply@forgecode.dev>

pkgname=forge
pkgver=0.1.0
pkgrel=1.cachy
pkgdesc="AI-enabled pair programmer for 300+ models (Claude, GPT, Grok, Deepseek, Gemini...)"
arch=('x86_64')
url="https://forgecode.dev"
license=('MIT')
depends=('git' 'fd')
optdepends=(
'bat: enhanced file previews in :doctor and the ZSH plugin'
'zsh: to use the : prefix ZSH plugin and theme'
'fzf: improved interactive pickers (some plugin features)'
)
makedepends=(
'cargo'
'cmake'
'nasm'
'perl'
'pkgconf'
'protobuf'
'sqlite'
)
provides=('forge')
conflicts=('forge-bin') # if an official bin package ever appears
options=('!lto' '!debug') # LTO is controlled by the Cargo release profile + RUSTFLAGS
source=()
sha256sums=()
# No separate .install file: post_install / post_upgrade are defined at the bottom of
# this PKGBUILD and are executed by makepkg/pacman directly. This keeps the packaging
# artifact count to the single PKGBUILD (plus the built package).

# When this PKGBUILD lives next to a full forge checkout (for local makepkg testing
# of CachyOS-optimized builds) we treat $startdir as the source tree.
# In a real clean-chroot / overlay build you would normally use a git source entry
# pointing at the desired branch/commit and a proper pkgver() function.
# The logic below makes both scenarios work without modification.
prepare() {
# If we were given a real source tarball/git clone by makepkg, use $srcdir.
# Otherwise fall back to the directory containing the PKGBUILD (local tree test).
if [ -d "$srcdir/forgecode" ]; then
cd "$srcdir/forgecode"
elif [ -f "$startdir/Cargo.toml" ] && [ -d "$startdir/crates/forge_main" ]; then
cd "$startdir"
else
# Last resort: assume user did `makepkg` while cwd is the forge tree
cd "$startdir"
fi

# Ensure we have a Cargo.lock (we do in the repo)
if [ ! -f Cargo.lock ]; then
cargo fetch --locked
fi
}

build() {
if [ -d "$srcdir/forgecode" ]; then
cd "$srcdir/forgecode"
elif [ -f "$startdir/Cargo.toml" ] && [ -d "$startdir/crates/forge_main" ]; then
cd "$startdir"
else
cd "$startdir"
fi

echo "==> Building with CachyOS-provided RUSTFLAGS (from makepkg.conf / chroot)"
echo " RUSTFLAGS=${RUSTFLAGS:-<empty - relying on makepkg.conf>}"
echo " APP_VERSION will be set to ${pkgver}-${pkgrel} for build.rs embedding"

export APP_VERSION="${pkgver}-${pkgrel}"

# --frozen for reproducibility inside the chroot (Cargo.lock is present)
cargo build --frozen --release -p forge_main --bin forge
}

package() {
if [ -d "$srcdir/forgecode" ]; then
cd "$srcdir/forgecode"
elif [ -f "$startdir/Cargo.toml" ] && [ -d "$startdir/crates/forge_main" ]; then
cd "$startdir"
else
cd "$startdir"
fi

install -Dm755 target/release/forge "$pkgdir/usr/bin/forge"

# License (required by Arch packaging guidelines)
install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE"

# Optional: ship the original shell-plugin sources under /usr/share for reference
# or for people who want to inspect / diff the embedded code. Not required at
# runtime because everything the binary needs is compiled in via include_dir!.
# Uncomment if your overlay wants to expose them:
#
# install -d "$pkgdir/usr/share/forge/shell-plugin"
# cp -a --no-preserve=ownership shell-plugin/* "$pkgdir/usr/share/forge/shell-plugin/"
}

# The .install file is written inline by makepkg when this PKGBUILD is processed
# (because we declared `install=forge.install` and the content follows).
#
# It only prints a friendly message after pacman -S / -U.
post_install() {
echo "==> Forge installed to /usr/bin/forge"
echo "==> To enable the fast :command ZSH plugin (recommended):"
echo " forge zsh setup"
echo " exec zsh"
echo "==> Then try :doctor or just type : followed by a prompt at your shell."
echo "==> CachyOS-optimized build (RUSTFLAGS from makepkg.conf at build time)."
}

post_upgrade() {
post_install
}

# vim: set ts=2 sw=2 et:
56 changes: 56 additions & 0 deletions crates/forge_infra/src/auth/strategy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1319,6 +1319,62 @@ mod tests {
assert!(matches!(actual.unwrap(), AnyAuthStrategy::CodexDevice(_)));
}

#[test]
fn test_create_auth_strategy_xai_oauth_code_uses_standard() {
// xAI is neither CLAUDE_CODE nor GITHUB_COPILOT, so the OAuthCode
// (SuperGrok loopback) flow must fall through to the generic
// StandardHttpProvider with zero per-provider code.
let config = OAuthConfig {
client_id: "b1a00492-073a-47ea-816f-4c329264a828".to_string().into(),
auth_url: Url::parse("https://auth.x.ai/oauth2/authorize").unwrap(),
token_url: Url::parse("https://auth.x.ai/oauth2/token").unwrap(),
scopes: vec!["api:access".to_string()],
redirect_uri: Some("http://127.0.0.1:56121/callback".to_string()),
use_pkce: true,
token_refresh_url: None,
extra_auth_params: None,
custom_headers: None,
};

let factory = ForgeAuthStrategyFactory;
let actual = factory
.create_auth_strategy(
ProviderId::XAI,
forge_domain::AuthMethod::OAuthCode(config),
vec![],
)
.unwrap();
assert!(matches!(actual, AnyAuthStrategy::OAuthCodeStandard(_)));
}

#[test]
fn test_create_auth_strategy_xai_oauth_device_uses_device() {
// The xAI headless device flow omits token_refresh_url, so it must
// route to the plain OAuthDevice strategy (RFC 8628), not the
// GitHub-Copilot OAuthWithApiKey hybrid.
let config = OAuthConfig {
client_id: "b1a00492-073a-47ea-816f-4c329264a828".to_string().into(),
auth_url: Url::parse("https://auth.x.ai/oauth2/device/code").unwrap(),
token_url: Url::parse("https://auth.x.ai/oauth2/token").unwrap(),
scopes: vec!["api:access".to_string()],
redirect_uri: None,
use_pkce: false,
token_refresh_url: None,
extra_auth_params: None,
custom_headers: None,
};

let factory = ForgeAuthStrategyFactory;
let actual = factory
.create_auth_strategy(
ProviderId::XAI,
forge_domain::AuthMethod::OAuthDevice(config),
vec![],
)
.unwrap();
assert!(matches!(actual, AnyAuthStrategy::OAuthDevice(_)));
}

/// Helper to build a JWT token with the given claims payload.
fn build_jwt(claims: &serde_json::Value) -> String {
use base64::Engine;
Expand Down
41 changes: 40 additions & 1 deletion crates/forge_repo/src/provider/provider.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,46 @@
"response_type": "OpenAI",
"url": "https://api.x.ai/v1/chat/completions",
"models": "https://api.x.ai/v1/models",
"auth_methods": ["api_key"]
"auth_methods": [
{
"oauth_code": {
"auth_url": "https://auth.x.ai/oauth2/authorize",
"token_url": "https://auth.x.ai/oauth2/token",
"client_id": "b1a00492-073a-47ea-816f-4c329264a828",
"scopes": [
"openid",
"profile",
"email",
"offline_access",
"grok-cli:access",
"api:access"
],
"redirect_uri": "http://127.0.0.1:56121/callback",
"use_pkce": true,
"extra_auth_params": {
"plan": "generic",
"referrer": "forgecode"
}
}
},
{
"oauth_device": {
"auth_url": "https://auth.x.ai/oauth2/device/code",
"token_url": "https://auth.x.ai/oauth2/token",
"client_id": "b1a00492-073a-47ea-816f-4c329264a828",
"scopes": [
"openid",
"profile",
"email",
"offline_access",
"grok-cli:access",
"api:access"
],
"use_pkce": false
}
},
"api_key"
]
},
{
"id": "openai",
Expand Down
78 changes: 78 additions & 0 deletions crates/forge_repo/src/provider/provider_repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,84 @@ mod tests {
);
}

#[test]
fn test_xai_oauth_config() {
let configs = get_provider_configs();
let config = configs.iter().find(|c| c.id == ProviderId::XAI).unwrap();

assert_eq!(config.id, ProviderId::XAI);
assert_eq!(config.api_key_vars, Some("XAI_API_KEY".to_string()));
assert_eq!(config.response_type, Some(ProviderResponse::OpenAI));
assert_eq!(config.url.as_str(), "https://api.x.ai/v1/chat/completions");

// Three auth methods: loopback OAuth, headless device OAuth, manual key.
assert_eq!(config.auth_methods.len(), 3);
assert!(config.auth_methods.contains(&AuthMethod::ApiKey));

let expected_scopes = vec![
"openid".to_string(),
"profile".to_string(),
"email".to_string(),
"offline_access".to_string(),
"grok-cli:access".to_string(),
"api:access".to_string(),
];

// Loopback authorization-code + PKCE (SuperGrok subscription).
let code = config
.auth_methods
.iter()
.find_map(|m| match m {
AuthMethod::OAuthCode(cfg) => Some(cfg),
_ => None,
})
.expect("xai should expose an oauth_code auth method");
assert_eq!(
code.client_id.as_str(),
"b1a00492-073a-47ea-816f-4c329264a828"
);
assert_eq!(code.auth_url.as_str(), "https://auth.x.ai/oauth2/authorize");
assert_eq!(code.token_url.as_str(), "https://auth.x.ai/oauth2/token");
assert_eq!(code.scopes, expected_scopes);
assert_eq!(
code.redirect_uri.as_deref(),
Some("http://127.0.0.1:56121/callback")
);
assert!(code.use_pkce);
let extra = code
.extra_auth_params
.as_ref()
.expect("oauth_code should set extra_auth_params");
// plan=generic is mandatory: xAI rejects loopback OAuth from
// non-allowlisted clients without it.
assert_eq!(extra.get("plan").map(String::as_str), Some("generic"));
assert_eq!(extra.get("referrer").map(String::as_str), Some("forgecode"));

// Headless device-code (remote / VPS). auth_url MUST be the
// device-authorization endpoint, and token_refresh_url must be absent
// so the factory routes to the plain device flow.
let device = config
.auth_methods
.iter()
.find_map(|m| match m {
AuthMethod::OAuthDevice(cfg) => Some(cfg),
_ => None,
})
.expect("xai should expose an oauth_device auth method");
assert_eq!(
device.client_id.as_str(),
"b1a00492-073a-47ea-816f-4c329264a828"
);
assert_eq!(
device.auth_url.as_str(),
"https://auth.x.ai/oauth2/device/code"
);
assert_eq!(device.token_url.as_str(), "https://auth.x.ai/oauth2/token");
assert_eq!(device.scopes, expected_scopes);
assert!(device.redirect_uri.is_none());
assert!(device.token_refresh_url.is_none());
}

#[test]
fn test_vertex_ai_config() {
let configs = get_provider_configs();
Expand Down
Loading
Loading