Skip to content
Draft
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
13 changes: 12 additions & 1 deletion apps/native/src-tauri/src/managed_edits/homebrew_adopt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const NIX_EVAL_HOMEBREW_APPLY: &str = r#"cfg: {
}"#;

const NIX_EVAL_HOMEBREW_ATTR_TEMPLATE: &str =
r#".#darwinConfigurations."{hostname}".config.homebrew"#;
r#".#darwinConfigurations.{hostname}.config.homebrew"#;

/// Checks if Homebrew is installed by trying to run `brew --version`.
fn is_homebrew_installed() -> bool {
Expand Down Expand Up @@ -845,6 +845,17 @@ mod tests {
std::fs::write(path, content).expect("failed to write test file");
}

#[test]
fn nix_eval_homebrew_attr_quotes_hostname_once() {
let safe_host_attr = serde_json::to_string("Coopers-MacBook-Pro").unwrap();
let attr = NIX_EVAL_HOMEBREW_ATTR_TEMPLATE.replace("{hostname}", &safe_host_attr);

assert_eq!(
attr,
r#".#darwinConfigurations."Coopers-MacBook-Pro".config.homebrew"#
);
}

#[test]
#[ignore = "Runs against the local system; enable explicitly when debugging the nix eval homebrew."]
#[cfg(target_os = "macos")]
Expand Down
10 changes: 10 additions & 0 deletions apps/native/src/components/widget/onboarding/lib/inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ interface ApiKeyProviderLike {
};
}

interface InferenceProvider {
id: "openrouter" | "openai";
name: string;
defaultModel: string;
prefsKeyField: "openrouterApiKey" | "openaiApiKey";
keyPrefix: string;
keyPlaceholder: string;
docsHint: string;
}

/**
* Bring-your-own-key providers, aligned to what the native backend actually
* supports (OpenRouter + OpenAI direct). Keys persist to UiPrefs and the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { NixSetupStep } from "@/components/widget/onboarding/steps/nix-setup-ste
import { PermissionsStep } from "@/components/widget/onboarding/steps/permissions-step";
import { SetupStep } from "@/components/widget/onboarding/steps/setup-step";
import { useOnboardingProgress } from "@/hooks/use-onboarding-progress";
import { hasConfiguredInference } from "@/lib/providers/ai-provider-validation";
import { onboardingActions, useOnboarding, useViewModel } from "@nixmac/state";

interface OnboardingStepContentProps {
Expand All @@ -17,12 +18,11 @@ interface OnboardingStepContentProps {
export function OnboardingStepContent({ currentStep, title }: OnboardingStepContentProps) {
const trackedCustomizations = useOnboarding((s) => s.trackedCustomizations);
const trackedCustomizationSources = useOnboarding((s) => s.trackedCustomizationSources);
// Inference readiness is a durable fact: provider + model are persisted to
// GlobalPreferences by InferenceSetup, and the login decision is recorded
// separately. The build step only needs to know inference is configured.
// Inference readiness is a durable fact: provider/model preferences are
// persisted by InferenceSetup, and the login decision is recorded separately.
const evolveProvider = useViewModel((s) => s.preferences?.evolveProvider ?? null);
const evolveModel = useViewModel((s) => s.preferences?.evolveModel ?? null);
const hasInference = Boolean(evolveProvider) && Boolean(evolveModel);
const hasInference = hasConfiguredInference(evolveProvider, evolveModel);
const { markMacScanned, markLoginDecided } = useOnboardingProgress();

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type StepId,
} from "@/components/widget/onboarding/lib/onboarding";
import { settings } from "@/lib/env";
import { hasConfiguredInference } from "@/lib/providers/ai-provider-validation";
import { onboardingActions, useOnboarding, useViewModel } from "@nixmac/state";
import { useCallback, useEffect, useMemo, useRef } from "react";

Expand Down Expand Up @@ -54,7 +55,7 @@ export function useOnboardingFlow(): {
flakeReady,
macScanned: macScannedAt !== null,
loginDecided,
hasInference: Boolean(evolveProvider) && Boolean(evolveModel),
hasInference: hasConfiguredInference(evolveProvider, evolveModel),
buildComplete: lastBuildAt !== null,
inferenceDeferred,
}),
Expand Down
6 changes: 0 additions & 6 deletions apps/native/src/components/widget/settings/ai-models-tab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,6 @@ interface AiModelsTabProps {
form: ReactFormExtendedApi<any, any, any, any, any, any, any, any, any, any, any, any>;
}

const CLI_PROVIDERS = [
{ value: "claude", label: "Claude CLI" },
{ value: "codex", label: "Codex CLI" },
{ value: "opencode", label: "OpenCode CLI" },
] as const;

function isPlainInputCliProvider(provider: string): boolean {
return provider === "claude" || provider === "codex";
}
Expand Down
15 changes: 15 additions & 0 deletions apps/native/src/lib/providers/ai-provider-validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";

import {
getProviderConfigInvalidReason,
hasConfiguredInference,
resolveOpenAiCompatibleProvider,
} from "./ai-provider-validation";

Expand Down Expand Up @@ -124,3 +125,17 @@ describe("getProviderConfigInvalidReason", () => {
).toBe("No model set");
});
});

describe("hasConfiguredInference", () => {
it("accepts CLI providers without a model so onboarding can finish with CLI defaults", () => {
expect(hasConfiguredInference("claude", "")).toBe(true);
expect(hasConfiguredInference("codex", null)).toBe(true);
expect(hasConfiguredInference("opencode", " ")).toBe(true);
});

it("still requires non-CLI providers to persist a model", () => {
expect(hasConfiguredInference("openrouter", "")).toBe(false);
expect(hasConfiguredInference("nixmac", "openai/gpt-4o-mini")).toBe(true);
expect(hasConfiguredInference(null, "openai/gpt-4o-mini")).toBe(false);
});
});
13 changes: 13 additions & 0 deletions apps/native/src/lib/providers/ai-provider-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ export function isCliProvider(provider: string): boolean {
return CLI_PROVIDER_VALUES.includes(provider as (typeof CLI_PROVIDER_VALUES)[number]);
}

export function hasConfiguredInference(
provider: string | null | undefined,
model: string | null | undefined,
): boolean {
if (!provider) {
return false;
}
if (isCliProvider(provider)) {
return true;
}
return hasValue(model);
}

export function resolveOpenAiCompatibleProvider(
provider: string | null | undefined,
prefs: Pick<DarwinPrefs, "openrouterApiKey" | "openaiApiKey">,
Expand Down
Loading