mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-08 02:14:37 +00:00
M3 follow-up: SETUP.md rewritten for single-project approach
Q: Should I just create an iOS target in the current scarf project?
Would that be easier?
A: Yes — single scarf.xcodeproj with two targets (scarf + scarf-ios)
is objectively easier than a separate scarf-ios.xcodeproj.
The original conservative recommendation (separate xcodeproj) was
rooted in my not wanting to hand-edit pbxproj. But you're the one
clicking through Xcode's UI to create the target, not me — Xcode
handles multi-target multi-platform projects natively, with zero
risk to the existing Mac target.
Rewrote SETUP.md to describe the single-project flow:
- `File → New → Target` inside the existing project (not a new
project file).
- Both targets share the same SPM package references — ScarfCore
is already there for Mac, you just add it + ScarfIOS to the
scarf-ios target via General → Frameworks.
- One Xcode window, one scheme switcher, unified signing/team
settings.
Also threaded in M3-specific smoke-test steps (connect to a real
host → see Dashboard load via Citadel SFTP snapshot) and added a
post-M3 troubleshooting entry for the `Cannot find 'Process' in
scope` error — it should never appear now that makeProcess is
`#if !os(iOS)`-guarded, but if it does it's a leaked Mac-only file
in the scarf-ios target membership.
Milestone status table in SETUP.md updated to reflect M3 shipped.
https://claude.ai/code/session_019yMRP6mwZWfzVrPTqevx2y
This commit is contained in:
+150
-125
@@ -1,170 +1,195 @@
|
||||
# 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.
|
||||
This folder contains the source tree for the iOS app (`scarf-ios`).
|
||||
The Xcode target itself is added to the **existing `scarf.xcodeproj`**
|
||||
as a second target alongside the Mac `scarf` target — **not** a
|
||||
separate `.xcodeproj`. One project, two targets.
|
||||
|
||||
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.
|
||||
onboarding state machine, Citadel-backed SSH client, SQLite-backed
|
||||
Dashboard — already lives in the shared SPM packages
|
||||
(`Packages/ScarfCore`, `Packages/ScarfIOS`) and is exercised by
|
||||
96 passing unit tests on Linux CI. The iOS target is mostly just a
|
||||
wrapper that assembles those packages behind a `@main` SwiftUI app.
|
||||
|
||||
## One-time: create the Xcode target
|
||||
Creating the target is a one-time ~5-minute step in Xcode's UI.
|
||||
|
||||
1. Open **Xcode** → **File → New → Project…**
|
||||
2. Choose **iOS → App**. Click Next.
|
||||
3. Fill in:
|
||||
## One-time: add the iOS target to the existing project
|
||||
|
||||
1. Open `scarf/scarf.xcodeproj` in **Xcode**.
|
||||
2. Select the project in the Project Navigator.
|
||||
3. **File → New → Target…** (or use the `+` button at the bottom of
|
||||
the targets list).
|
||||
4. Choose **iOS → App**. Click Next.
|
||||
5. Fill in:
|
||||
- **Product Name**: `scarf-ios`
|
||||
- **Team**: `3Q6X2L86C4` (the same team the Mac app uses for notarization)
|
||||
- **Team**: `3Q6X2L86C4` (the same team the Mac target uses)
|
||||
- **Organization Identifier**: `com.scarf`
|
||||
- **Bundle Identifier** will be `com.scarf.scarf-ios`
|
||||
- **Interface**: **SwiftUI**
|
||||
- **Language**: **Swift**
|
||||
- **Storage**: **None** (no Core Data, no CloudKit)
|
||||
- **Storage**: **None**
|
||||
- **Include Tests**: unchecked (SPM covers them)
|
||||
4. On the save-location sheet, navigate to `<repo>/scarf/` (the same
|
||||
level as `scarf.xcodeproj`) and hit **Create**.
|
||||
5. Xcode produces `<repo>/scarf/scarf-ios/scarf-ios.xcodeproj` and a
|
||||
default source tree you'll immediately throw away.
|
||||
6. Click **Finish**. Xcode creates the target plus a default source
|
||||
tree under `scarf/scarf-ios/` (a folder you'll immediately throw
|
||||
away, since our real source lives there already).
|
||||
7. If Xcode asks about "Activate scheme": **yes**.
|
||||
|
||||
## One-time: set project settings
|
||||
## One-time: target settings
|
||||
|
||||
In the **scarf-ios** project (target of the same name):
|
||||
With the `scarf-ios` target selected in the project editor:
|
||||
|
||||
1. **General → Minimum Deployments → iPhone**: `iOS 18.0`.
|
||||
2. **General → Supported Destinations**: keep **iPhone** only. Remove
|
||||
iPad + Mac Catalyst + Vision.
|
||||
3. **Info → Bundle Identifier**: `com.scarf.scarf-ios`.
|
||||
4. **Signing & Capabilities → Team**: `3Q6X2L86C4` (same as Mac).
|
||||
5. **Build Settings → Swift Language Version**: `Swift 5` (matches
|
||||
the Mac app and both SPM packages).
|
||||
3. **Signing & Capabilities → Team**: `3Q6X2L86C4` (should have
|
||||
auto-filled from step 5 above).
|
||||
4. **Build Settings → Swift Language Version**: `Swift 5` (matches
|
||||
the Mac target + both SPM packages).
|
||||
|
||||
## One-time: wire the SPM packages
|
||||
## One-time: link the SPM packages to the iOS target
|
||||
|
||||
1. **File → Add Package Dependencies…**
|
||||
2. **Add Local…** button in the lower-left of the dialog.
|
||||
3. Select `<repo>/scarf/Packages/ScarfCore`. Click **Add Package**.
|
||||
4. Target to attach it to: **scarf-ios**. Click **Add Package**.
|
||||
5. Repeat steps 1–4 for `<repo>/scarf/Packages/ScarfIOS`.
|
||||
- `ScarfIOS` already declares `Citadel` as 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.
|
||||
`ScarfCore` and `ScarfIOS` are already in the project's package
|
||||
references (the Mac target already uses ScarfCore). You only need to
|
||||
add them to the NEW iOS target.
|
||||
|
||||
1. Select the project in the navigator → select the `scarf-ios`
|
||||
target → **General → Frameworks, Libraries, and Embedded
|
||||
Content**.
|
||||
2. Click the `+` button.
|
||||
3. Select **ScarfCore** (library from ScarfCore local package). Add.
|
||||
4. Click `+` again. Select **ScarfIOS**. Add.
|
||||
5. Citadel is pulled in transitively by ScarfIOS — no need to add it
|
||||
explicitly. On first build Xcode resolves it from GitHub
|
||||
(~30s-1min).
|
||||
|
||||
## One-time: replace the default source tree
|
||||
|
||||
1. In Xcode's Project Navigator, delete the auto-generated files
|
||||
Xcode created for you:
|
||||
1. In Xcode's Project Navigator, find the `scarf-ios` group Xcode
|
||||
created. Delete:
|
||||
- `scarf_iosApp.swift` (or `scarf-iosApp.swift`)
|
||||
- `ContentView.swift`
|
||||
- **`Assets.xcassets`** — yes, delete this one too. We ship a
|
||||
pre-built `Assets.xcassets/` with the app icon + accent
|
||||
color inside `<repo>/scarf/scarf-ios/` that we'll add in the
|
||||
next step.
|
||||
2. In Finder, open `<repo>/scarf/scarf-ios/`. Drag **App/**,
|
||||
**Onboarding/**, **Dashboard/**, and **`Assets.xcassets/`** onto
|
||||
the `scarf-ios` target in Xcode's navigator.
|
||||
- In the import sheet: **Create groups**, **Add to target:
|
||||
scarf-ios**.
|
||||
3. Build (`⌘B`). It should compile cleanly against Citadel 0.12.1
|
||||
— every API call in `CitadelSSHService` was 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 is
|
||||
`SSHAuthenticationMethod.ed25519(username:privateKey:)` being
|
||||
renamed or refactored; check the current
|
||||
`Sources/Citadel/SSHAuthenticationMethod.swift` in the new
|
||||
release.
|
||||
- **`Assets.xcassets`** — we ship our own pre-built one.
|
||||
2. In Finder, open `<repo>/scarf/scarf-ios/`. Drag these four folders
|
||||
onto the `scarf-ios` group in Xcode:
|
||||
- `App/`
|
||||
- `Onboarding/`
|
||||
- `Dashboard/`
|
||||
- `Assets.xcassets/`
|
||||
3. In the import sheet:
|
||||
- **Destination**: Copy items if needed — **unchecked** (they're
|
||||
already in place).
|
||||
- **Added folders**: **Create groups**.
|
||||
- **Add to targets**: **scarf-ios** only.
|
||||
4. Build (`⌘B`). Should compile cleanly against Citadel 0.12.1 —
|
||||
every API call in `CitadelSSHService` + `CitadelServerTransport`
|
||||
was cross-checked against the 0.12.1 tag. If you've bumped the
|
||||
pin to 0.13+ and something fails, check
|
||||
`Sources/Citadel/SSHAuthenticationMethod.swift` for the current
|
||||
`.ed25519(username:privateKey:)` spelling.
|
||||
|
||||
## App icon + accent color
|
||||
|
||||
The `Assets.xcassets/` inside `<repo>/scarf/scarf-ios/` ships with:
|
||||
Already in `Assets.xcassets/` so you don't configure anything:
|
||||
|
||||
- **`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.722` in light mode; lighter `0.400 / 0.690
|
||||
/ 0.902` in dark mode). If you want a different accent, swap
|
||||
the sRGB components in `Contents.json` or edit in Xcode's
|
||||
color picker.
|
||||
icon copied from the Mac app's icon set. iOS 14+ renders all
|
||||
smaller sizes automatically from the single 1024 image.
|
||||
- **`AccentColor.colorset`** — custom Scarf teal (sRGB `0.227 /
|
||||
0.525 / 0.722` light mode; `0.400 / 0.690 / 0.902` dark). Edit
|
||||
`Contents.json` or Xcode's color picker to change.
|
||||
|
||||
## Info.plist additions for M2
|
||||
## Info.plist for TestFlight
|
||||
|
||||
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.
|
||||
Under the scarf-ios target → **Info → Custom iOS Target Properties**:
|
||||
|
||||
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)
|
||||
- `LSRequiresIPhoneOS = YES` (usually defaulted)
|
||||
- `UIApplicationSceneManifest → UIApplicationSupportsMultipleScenes = NO`
|
||||
(single-window for iPhone)
|
||||
- `UILaunchScreen` — empty dictionary is fine.
|
||||
(iPhone single-window)
|
||||
- `UILaunchScreen` — empty dictionary is fine
|
||||
|
||||
## Smoke test the target
|
||||
Citadel uses SwiftNIO, not Apple's local-network discovery, so no
|
||||
network-usage-description key is needed.
|
||||
|
||||
1. Pick an iPhone simulator (any iPhone running iOS 18+) and hit ⌘R.
|
||||
2. You should see the onboarding flow: **Remote host** form → **SSH
|
||||
key** choice → **Generate** → **Show public key** → …
|
||||
3. On **Show public key**, the OpenSSH line is selectable and
|
||||
copy-able. The text renders as `ssh-ed25519 AAAA… scarf-iphone-XXXX`.
|
||||
4. Tap **I've added this key**. Onboarding calls `CitadelSSHService`
|
||||
to 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.
|
||||
5. 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.
|
||||
## Smoke test
|
||||
|
||||
## TestFlight (still M2 — optional)
|
||||
1. Switch the run destination to an iPhone simulator (any iPhone
|
||||
running iOS 18+). Xcode's target switcher lets you toggle between
|
||||
scarf (macOS) and scarf-ios.
|
||||
2. ⌘R. Expect the onboarding flow:
|
||||
**Remote host** → **SSH key choice** → **Generate** → **Show
|
||||
public key** → **I've added this** → **Test connection** → ...
|
||||
3. On **Show public key**, the OpenSSH line is selectable + copyable
|
||||
(`ssh-ed25519 AAAA… scarf-iphone-XXXX`).
|
||||
4. Without a real SSH server, **Test connection** will fail with
|
||||
`.hostUnreachable` — that's expected. Land on the **Connection
|
||||
failed** screen with a **Retry** button.
|
||||
5. Real end-to-end: use a host you own. Copy the shown public key
|
||||
into `~/.ssh/authorized_keys` on that host. Tap **Retry**. You
|
||||
should reach **Connected** → **Dashboard**, which then does a
|
||||
Citadel SFTP snapshot of `~/.hermes/state.db` and renders
|
||||
session + token stats.
|
||||
|
||||
1. **Product → Archive** (select a physical iPhone or "Any iOS
|
||||
Device (arm64)" as the target).
|
||||
2. **Window → Organizer → Archives → Distribute App → App Store
|
||||
Connect → Upload**. Uses your existing team signing setup.
|
||||
3. First upload will trigger App Store Connect to create the app
|
||||
record if it doesn't exist. Give it `com.scarf.scarf-ios` and
|
||||
the same team.
|
||||
4. After processing, invite testers from App Store Connect.
|
||||
## TestFlight upload
|
||||
|
||||
## What's *not* in M2
|
||||
1. Scheme selector top-left → **scarf-ios**. Destination: **Any iOS
|
||||
Device (arm64)** or a physical iPhone.
|
||||
2. **Product → Archive**.
|
||||
3. **Window → Organizer → Archives → Distribute App → App Store
|
||||
Connect → Upload**.
|
||||
4. First upload creates the App Store Connect app record with
|
||||
`com.scarf.scarf-ios`. Same team as the Mac app.
|
||||
5. Invite testers from App Store Connect.
|
||||
|
||||
- Dashboard data (sessions, messages, stats) — **M3** adds a
|
||||
Citadel-backed `ServerTransport` so `HermesDataService` and friends
|
||||
work over SSH from iOS.
|
||||
- Chat — **M4** adds an `SSHExecACPChannel` (the iOS counterpart to
|
||||
`ProcessACPChannel`) so `ACPClient` runs over a Citadel exec
|
||||
session.
|
||||
- Memory editing, Cron, Skills, Settings — **M5**.
|
||||
- Polish, App Store submission — **M6**.
|
||||
## What's in each milestone
|
||||
|
||||
- **M2** — Onboarding (SSH key + Keychain), Citadel-based
|
||||
"Test Connection", Dashboard placeholder.
|
||||
- **M3** — Real Dashboard via `CitadelServerTransport` +
|
||||
`HermesDataService` (you're running the M3 PR now).
|
||||
- **M4** — Chat over an iOS `SSHExecACPChannel`.
|
||||
- **M5** — Memory editing, Cron, Skills, Settings.
|
||||
- **M6** — Polish + App Store submission.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Citadel fails to resolve.** Delete derived data (`~/Library/Developer/Xcode/DerivedData/scarf-ios-*`)
|
||||
and `File → Packages → Reset Package Caches`, then rebuild.
|
||||
**Citadel fails to resolve.** Delete DerivedData
|
||||
(`~/Library/Developer/Xcode/DerivedData/scarf-*`) 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.
|
||||
**`Cannot find 'Process' in scope` when building scarf-ios.** Should
|
||||
not happen post-M3 — the `makeProcess` protocol method is now
|
||||
`#if !os(iOS)`-guarded. If you see this: grep the scarf-ios target's
|
||||
source files for `Process()`, `process.isRunning`, or `terminationHandler`
|
||||
— something Mac-only leaked into the iOS target's membership.
|
||||
|
||||
**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.
|
||||
**`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(...)` in ScarfIOS — one
|
||||
line to update. Keep the protocol conformance intact.
|
||||
|
||||
**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.
|
||||
**Dashboard shows "Couldn't read the Hermes database".** Expected if
|
||||
the host's `~/.hermes/state.db` doesn't exist yet (fresh Hermes
|
||||
install). Start a Hermes session on the host first, then pull-to-
|
||||
refresh.
|
||||
|
||||
**Dashboard spins forever on first connect.** Citadel connection
|
||||
hand-shake + SFTP open can take 5-10s on a cold network. If it's
|
||||
longer, check that the same SSH key works from a regular `ssh` client
|
||||
against the same host — sometimes the issue is an authorized_keys
|
||||
line with a trailing whitespace, or the host uses a restricted shell
|
||||
that blocks `sqlite3`.
|
||||
|
||||
**Keychain reads empty after relaunch.** Check you haven't set
|
||||
`kSecAttrAccessible` to a biometric-required value. We use
|
||||
`AfterFirstUnlockThisDeviceOnly`, which is readable any time after
|
||||
the first post-boot unlock.
|
||||
|
||||
**The public-key line doesn't match what `ssh-keygen` would produce
|
||||
from the same private key.** The **public** key is standard OpenSSH
|
||||
wire format (interop-safe with `authorized_keys`). The **private**
|
||||
key uses a compact Scarf-internal PEM (see
|
||||
`ScarfIOS/Ed25519KeyGenerator.swift`) — it's not directly exportable
|
||||
to `~/.ssh/id_ed25519`. A future phase adds an export flow that
|
||||
re-serializes to standard OpenSSH PEM.
|
||||
|
||||
Reference in New Issue
Block a user