Two follow-ups per review:
## Citadel: current stable
Citadel is at 0.12.1, not 0.9.x as I'd been writing against. Bumped
the pin from `from: "0.7.0"` to `.upToNextMinor(from: "0.12.0")`
— tight because Citadel's pre-1.0 authentication-method variants
have shifted between minor releases (0.7 → 0.9 → 0.12), so
explicit bump-and-review is safer than letting the version float.
Downloaded Citadel 0.12.1's source and verified every API call in
CitadelSSHService against it:
- SSHAuthenticationMethod.ed25519(username:, privateKey:) ✓
- SSHClientSettings(host:, authenticationMethod:, hostKeyValidator:) ✓
- SSHHostKeyValidator.acceptAnything() ✓
- SSHClient.connect(to: settings) ✓
- client.executeCommand(_:) -> ByteBuffer ✓
- client.close() async throws ✓
Dropped the "FIXME — may need adjustment" disclaimer in the file
header; replaced with a "verified against 0.12.1" note that says
re-verify if the pin bumps to 0.13+. Same change in SETUP.md
troubleshooting.
## Assets.xcassets (app icon + accent color)
scarf/scarf-ios/Assets.xcassets/ now exists with:
- AppIcon.appiconset/
AppIcon-1024.png (1024×1024, copied from the Mac app's
icon set — same art)
Contents.json (idiom: universal, platform: ios,
size: 1024x1024 — iOS 14+ renders all
smaller sizes from this automatically)
- AccentColor.colorset/
Contents.json (Scarf teal: sRGB 0.227/0.525/0.722
light, 0.400/0.690/0.902 dark)
- Contents.json (root, empty — just version metadata)
SETUP.md updated:
- Instructs Alan to delete Xcode's scaffolded Assets.xcassets AND
import ours, not the other way around.
- Notes the accent color values so a different palette choice is
a one-file edit.
- Removes the obsolete "drop your icon asset" step.
No functional code changes; tests still 88/88 on Linux.
https://claude.ai/code/session_019yMRP6mwZWfzVrPTqevx2y
7.5 KiB
scarf-ios — Xcode target setup
This folder contains the source tree for the iOS app (scarf-ios), but
not the Xcode project file. Creating the .xcodeproj is a one-time
step you do in Xcode's UI — it's about 5 minutes, and doing it by hand
produces a project file that's definitely correct for whichever Xcode
version you're running, rather than a hand-edited pbxproj that might
drift from Xcode's expectations.
Everything the app needs — SSH key generation, Keychain storage,
onboarding state machine, Citadel-backed connection testing — already
lives in the shared SPM packages (Packages/ScarfCore,
Packages/ScarfIOS) and is exercised by 88 passing unit tests on
Linux CI. The Xcode target is mostly just a wrapper that assembles
those packages behind a @main SwiftUI app.
One-time: create the Xcode target
- Open Xcode → File → New → Project…
- Choose iOS → App. Click Next.
- Fill in:
- Product Name:
scarf-ios - Team:
3Q6X2L86C4(the same team the Mac app uses for notarization) - Organization Identifier:
com.scarf - Interface: SwiftUI
- Language: Swift
- Storage: None (no Core Data, no CloudKit)
- Include Tests: unchecked (SPM covers them)
- Product Name:
- On the save-location sheet, navigate to
<repo>/scarf/(the same level asscarf.xcodeproj) and hit Create. - Xcode produces
<repo>/scarf/scarf-ios/scarf-ios.xcodeprojand a default source tree you'll immediately throw away.
One-time: set project settings
In the scarf-ios project (target of the same name):
- General → Minimum Deployments → iPhone:
iOS 18.0. - General → Supported Destinations: keep iPhone only. Remove iPad + Mac Catalyst + Vision.
- Info → Bundle Identifier:
com.scarf.scarf-ios. - Signing & Capabilities → Team:
3Q6X2L86C4(same as Mac). - Build Settings → Swift Language Version:
Swift 5(matches the Mac app and both SPM packages).
One-time: wire the SPM packages
- File → Add Package Dependencies…
- Add Local… button in the lower-left of the dialog.
- Select
<repo>/scarf/Packages/ScarfCore. Click Add Package. - Target to attach it to: scarf-ios. Click Add Package.
- Repeat steps 1–4 for
<repo>/scarf/Packages/ScarfIOS.ScarfIOSalready declaresCitadelas a dependency — Xcode will resolve it automatically when you add the local package.- On first resolution expect a ~30s wait while Citadel + SwiftNIO-SSH fetch from GitHub.
One-time: replace the default source tree
- In Xcode's Project Navigator, delete the auto-generated files
Xcode created for you:
scarf_iosApp.swift(orscarf-iosApp.swift)ContentView.swiftAssets.xcassets— yes, delete this one too. We ship a pre-builtAssets.xcassets/with the app icon + accent color inside<repo>/scarf/scarf-ios/that we'll add in the next step.
- In Finder, open
<repo>/scarf/scarf-ios/. Drag App/, Onboarding/, Dashboard/, andAssets.xcassets/onto thescarf-iostarget in Xcode's navigator.- In the import sheet: Create groups, Add to target: scarf-ios.
- Build (
⌘B). It should compile cleanly against Citadel 0.12.1 — every API call inCitadelSSHServicewas cross-checked against the 0.12.1 tag in April 2026. If you've bumped the pin to 0.13+ and something fails here, the likeliest culprit isSSHAuthenticationMethod.ed25519(username:privateKey:)being renamed or refactored; check the currentSources/Citadel/SSHAuthenticationMethod.swiftin the new release.
App icon + accent color
The Assets.xcassets/ inside <repo>/scarf/scarf-ios/ ships with:
AppIcon.appiconset/AppIcon-1024.png— the 1024×1024 Scarf icon from the Mac app's icon set. iOS 14+ renders all smaller sizes automatically from the single 1024 image.AccentColor.colorset— a custom Scarf teal (RGB 0.227 / 0.525 / 0.722in light mode; lighter0.400 / 0.690 / 0.902in dark mode). If you want a different accent, swap the sRGB components inContents.jsonor edit in Xcode's color picker.
Info.plist additions for M2
None required. Citadel uses SwiftNIO, which doesn't need the
network-usage Info.plist key unless you hit local-network
discovery — which onboarding doesn't.
If you want to publish to TestFlight in M2, add these under Info → Custom iOS Target Properties:
LSRequiresIPhoneOS = YES(defaults to YES, usually already set)UIApplicationSceneManifest → UIApplicationSupportsMultipleScenes = NO(single-window for iPhone)UILaunchScreen— empty dictionary is fine.
Smoke test the target
- Pick an iPhone simulator (any iPhone running iOS 18+) and hit ⌘R.
- You should see the onboarding flow: Remote host form → SSH key choice → Generate → Show public key → …
- On Show public key, the OpenSSH line is selectable and
copy-able. The text renders as
ssh-ed25519 AAAA… scarf-iphone-XXXX. - Tap I've added this key. Onboarding calls
CitadelSSHServiceto connect. With no real server, this will fail with.hostUnreachable— that's expected. You should land on the Connection failed screen with a "Retry" button. - Real end-to-end test: use a host you actually own, copy the shown
public key into its
~/.ssh/authorized_keys, tap Retry. You should reach the Connected state and then the placeholder Dashboard with a Disconnect button.
TestFlight (still M2 — optional)
- Product → Archive (select a physical iPhone or "Any iOS Device (arm64)" as the target).
- Window → Organizer → Archives → Distribute App → App Store Connect → Upload. Uses your existing team signing setup.
- First upload will trigger App Store Connect to create the app
record if it doesn't exist. Give it
com.scarf.scarf-iosand the same team. - After processing, invite testers from App Store Connect.
What's not in M2
- Dashboard data (sessions, messages, stats) — M3 adds a
Citadel-backed
ServerTransportsoHermesDataServiceand friends work over SSH from iOS. - Chat — M4 adds an
SSHExecACPChannel(the iOS counterpart toProcessACPChannel) soACPClientruns over a Citadel exec session. - Memory editing, Cron, Skills, Settings — M5.
- Polish, App Store submission — M6.
Troubleshooting
Citadel fails to resolve. Delete derived data (~/Library/Developer/Xcode/DerivedData/scarf-ios-*)
and File → Packages → Reset Package Caches, then rebuild.
SSHAuthenticationMethod has no member ed25519. Shouldn't
happen against Citadel 0.12.1 (verified), but historically the
private-key variant names have changed between minor versions
(0.7 → 0.9 → 0.12). See CitadelSSHService.buildClientSettings(...)
— there's one line to
update. Keep the protocol conformance intact.
Keychain reads empty after relaunch. Check that you haven't
accidentally set kSecAttrAccessible to a value that requires
biometric unlock — M2 uses AfterFirstUnlockThisDeviceOnly which
should always be readable.
The shown public-key line doesn't match what OpenSSH generates
from the private key. It won't — scarf-ios uses a compact
internal PEM shape for the private key (see Ed25519KeyGenerator
for the format). The public key is standard OpenSSH wire format
and is interop-safe with authorized_keys. If you want to export
the private key for use with ssh, that export flow is deferred
to a future phase.