From 00704412439760f9d0cbfac801551f1537370df0 Mon Sep 17 00:00:00 2001 From: Alan Wizemann Date: Sat, 9 May 2026 19:03:06 +0200 Subject: [PATCH] feat(profiles): add --no-skills toggle to create-profile sheet MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an "Empty profile (no skills)" toggle to the Mac create-profile sheet, gated on `hasProfileNoSkills` (v0.13+). When ON, the create flow appends `--no-skills` to `hermes profile create`. The toggle is disabled (greyed out) when "Full copy of active profile" is on, per WS-7 plan Decision H — a full clone copies skills wholesale, so `--no-skills` would be a contradiction at the UX layer. The wire itself stays permissive: a user can stack `--clone --no-skills` to clone config but skip skills, which is a plausible workflow. Defensive write-strip: even though the toggle is hidden on pre-v0.13 hosts, the call site reads `createNoSkills` through the capability gate so a stale state value can't sneak `--no-skills` past argparse on a CLI that doesn't know it. iOS Profiles is read-only (per CLAUDE.md "v0.12 iOS catch-up Phase H") so no toggle there. TODO marker (WS-7-Q8) flags the assumed `--clone-all` interaction — verify Hermes's behaviour with both flags during integration. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ViewModels/ProfilesViewModel.swift | 9 +++++- .../Profiles/Views/ProfilesView.swift | 29 +++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/scarf/scarf/Features/Profiles/ViewModels/ProfilesViewModel.swift b/scarf/scarf/Features/Profiles/ViewModels/ProfilesViewModel.swift index d71a4b6..6739d54 100644 --- a/scarf/scarf/Features/Profiles/ViewModels/ProfilesViewModel.swift +++ b/scarf/scarf/Features/Profiles/ViewModels/ProfilesViewModel.swift @@ -112,10 +112,17 @@ final class ProfilesViewModel { } } - func create(name: String, cloneConfig: Bool, cloneAll: Bool) { + func create(name: String, cloneConfig: Bool, cloneAll: Bool, noSkills: Bool = false) { var args = ["profile", "create", name] if cloneAll { args.append("--clone-all") } else if cloneConfig { args.append("--clone") } + // v0.13+: Empty-profile creation. The wire is independent of + // --clone / --clone-all per the v0.13 release notes — the user + // can stack `--clone --no-skills` to clone config but skip + // skills, which is a plausible workflow. The UI still disables + // the toggle under --clone-all (Decision H, see ProfilesView) + // but the wire is permissive. + if noSkills { args.append("--no-skills") } runAndReload(args, success: "Profile '\(name)' created") } diff --git a/scarf/scarf/Features/Profiles/Views/ProfilesView.swift b/scarf/scarf/Features/Profiles/Views/ProfilesView.swift index 21f48b5..e0186e8 100644 --- a/scarf/scarf/Features/Profiles/Views/ProfilesView.swift +++ b/scarf/scarf/Features/Profiles/Views/ProfilesView.swift @@ -11,7 +11,12 @@ struct ProfilesView: View { @State private var createName = "" @State private var createCloneConfig = true @State private var createCloneAll = false + /// v0.13+ `--no-skills` toggle. Mutually exclusive with `--clone-all` + /// at the UX layer (Decision H from the WS-7 plan): a full clone + /// copies skills wholesale — `--no-skills` would be a contradiction. + @State private var createNoSkills = false @State private var showRename = false + @Environment(\.hermesCapabilities) private var capabilitiesStore init(context: ServerContext) { _viewModel = State(initialValue: ProfilesViewModel(context: context)) @@ -123,7 +128,7 @@ struct ProfilesView: View { } Spacer() Button { - createName = ""; createCloneConfig = true; createCloneAll = false + createName = ""; createCloneConfig = true; createCloneAll = false; createNoSkills = false showCreate = true } label: { Label("Create", systemImage: "plus") @@ -300,11 +305,31 @@ struct ProfilesView: View { Toggle("Clone config, .env, SOUL.md from active profile", isOn: $createCloneConfig) .disabled(createCloneAll) Toggle("Full copy of active profile (all state)", isOn: $createCloneAll) + // TODO(WS-7-Q8): Decision H — disable --no-skills when --clone-all + // is on. A full clone copies skills wholesale; --no-skills would + // be a contradiction. Verify Hermes's behaviour with both flags + // (argparse mutual exclusion vs. last-flag-wins vs. clone-but- + // skip-skills) and relax the disabled state if Hermes does + // something useful with the combination. + if capabilitiesStore?.capabilities.hasProfileNoSkills ?? false { + Toggle("Empty profile (no skills)", isOn: $createNoSkills) + .disabled(createCloneAll) + } HStack { Spacer() Button("Cancel") { showCreate = false } Button("Create") { - viewModel.create(name: createName, cloneConfig: createCloneConfig, cloneAll: createCloneAll) + viewModel.create( + name: createName, + cloneConfig: createCloneConfig, + cloneAll: createCloneAll, + // Defensive: if the toggle isn't visible (pre-v0.13) + // the state is always `false`, but read it through + // the capability gate anyway so a stale state value + // can't sneak `--no-skills` to a CLI that doesn't + // know it. + noSkills: (capabilitiesStore?.capabilities.hasProfileNoSkills ?? false) ? createNoSkills : false + ) showCreate = false } .buttonStyle(.borderedProminent)