axiom-deep-link-debugging by charleswiltgen/axiom
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-deep-link-debugging在以下情况使用:
simulator-tester 代理或 /axiom:screenshot 集成请勿用于:
axiom-swiftui-nav 技能)→ 添加仅调试用的 URL 方案以启用 xcrun simctl openurl 导航
→ 为每个屏幕创建调试深度链接,可从模拟器调用
→ 添加既能导航又能配置状态的调试链接
如果您遇到以下任何情况,请添加调试深度链接:
测试摩擦:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
调试效率低下:
解决方案:添加调试深度链接,让您(和 Claude Code)能够直接跳转到任何具有任何状态配置的屏幕。
添加一个仅调试用的 URL 方案,用于路由到屏幕。
import SwiftUI
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
#if DEBUG
.onOpenURL { url in
handleDebugURL(url)
}
#endif
}
}
#if DEBUG
private func handleDebugURL(_ url: URL) {
guard url.scheme == "debug" else { return }
// 基于 host 进行路由
switch url.host {
case "settings":
// 导航到设置
NotificationCenter.default.post(
name: .navigateToSettings,
object: nil
)
case "profile":
// 导航到个人资料
let userID = url.queryItems?["id"] ?? "current"
NotificationCenter.default.post(
name: .navigateToProfile,
object: userID
)
case "reset":
// 重置应用到初始状态
resetApp()
default:
print("⚠️ Unknown debug URL: \(url)")
}
}
#endif
}
#if DEBUG
extension Notification.Name {
static let navigateToSettings = Notification.Name("navigateToSettings")
static let navigateToProfile = Notification.Name("navigateToProfile")
}
extension URL {
var queryItems: [String: String]? {
guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false),
let items = components.queryItems else {
return nil
}
return Dictionary(uniqueKeysWithValues: items.map { ($0.name, $0.value ?? "") })
}
}
#endif
用法:
# 从模拟器
xcrun simctl openurl booted "debug://settings"
xcrun simctl openurl booted "debug://profile?id=123"
xcrun simctl openurl booted "debug://reset"
将调试深度链接与 NavigationStack 集成以实现稳健的导航。
import SwiftUI
@MainActor
class DebugRouter: ObservableObject {
@Published var path = NavigationPath()
#if DEBUG
func handleDebugURL(_ url: URL) {
guard url.scheme == "debug" else { return }
switch url.host {
case "settings":
path.append(Destination.settings)
case "recipe":
if let id = url.queryItems?["id"], let recipeID = Int(id) {
path.append(Destination.recipe(id: recipeID))
}
case "recipe-edit":
if let id = url.queryItems?["id"], let recipeID = Int(id) {
// 导航到食谱,然后到编辑
path.append(Destination.recipe(id: recipeID))
path.append(Destination.recipeEdit(id: recipeID))
}
case "reset":
path = NavigationPath() // 弹出到根视图
default:
print("⚠️ Unknown debug URL: \(url)")
}
}
#endif
}
struct ContentView: View {
@StateObject private var router = DebugRouter()
var body: some View {
NavigationStack(path: $router.path) {
HomeView()
.navigationDestination(for: Destination.self) { destination in
destinationView(for: destination)
}
}
#if DEBUG
.onOpenURL { url in
router.handleDebugURL(url)
}
#endif
}
@ViewBuilder
private func destinationView(for destination: Destination) -> some View {
switch destination {
case .settings:
SettingsView()
case .recipe(let id):
RecipeDetailView(recipeID: id)
case .recipeEdit(let id):
RecipeEditView(recipeID: id)
}
}
}
enum Destination: Hashable {
case settings
case recipe(id: Int)
case recipeEdit(id: Int)
}
用法:
# 导航到设置
xcrun simctl openurl booted "debug://settings"
# 导航到食谱 #42
xcrun simctl openurl booted "debug://recipe?id=42"
# 导航到食谱 #42 编辑屏幕
xcrun simctl openurl booted "debug://recipe-edit?id=42"
# 弹出到根视图
xcrun simctl openurl booted "debug://reset"
既能导航又能配置状态的调试链接。
#if DEBUG
extension DebugRouter {
func handleDebugURL(_ url: URL) {
guard url.scheme == "debug" else { return }
switch url.host {
case "login":
// 显示登录屏幕
path.append(Destination.login)
case "login-error":
// 显示登录屏幕并带有错误状态
path.append(Destination.login)
// 触发错误状态
NotificationCenter.default.post(
name: .showLoginError,
object: "Invalid credentials"
)
case "recipe-empty":
// 以空状态显示食谱列表
UserDefaults.standard.set(true, forKey: "debug_emptyRecipeList")
path.append(Destination.recipes)
case "recipe-error":
// 显示带有网络错误的食谱列表
UserDefaults.standard.set(true, forKey: "debug_networkError")
path.append(Destination.recipes)
default:
print("⚠️ Unknown debug URL: \(url)")
}
}
}
#endif
用法:
# 测试登录错误状态
xcrun simctl openurl booted "debug://login-error"
# 测试空食谱列表
xcrun simctl openurl booted "debug://recipe-empty"
# 测试网络错误处理
xcrun simctl openurl booted "debug://recipe-error"
仅在调试构建中注册调试 URL 方案。
步骤 1:将方案添加到 Info.plist
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>debug</string>
</array>
<key>CFBundleURLName</key>
<string>com.example.debug</string>
</dict>
</array>
步骤 2:从发布构建中剥离
在目标的构建阶段添加一个运行脚本阶段(在"复制捆绑资源"之前运行):
# 从 Release 构建中剥离调试 URL 方案
if [ "${CONFIGURATION}" = "Release" ]; then
echo "Removing debug URL scheme from Info.plist"
/usr/libexec/PlistBuddy -c "Delete :CFBundleURLTypes:0" "${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}" 2>/dev/null || true
fi
替代方案:在构建设置中为调试版和发布版配置使用单独的 Info.plist 文件。
/axiom:screenshot 命令# 1. 导航到屏幕
xcrun simctl openurl booted "debug://settings"
# 2. 等待导航完成
sleep 1
# 3. 捕获截图
/axiom:screenshot
simulator-tester 代理只需告诉代理:
代理将使用您的调试深度链接进行导航。
在添加调试深度链接之前,务必完成以下步骤:
列出测试所需的所有屏幕:
- 设置屏幕
- 个人资料屏幕(带有特定用户 ID)
- 食谱详情(带有特定食谱 ID)
- 错误状态(登录错误、网络错误等)
- 空状态(无食谱、无收藏)
debug://screen-name # 简单的屏幕导航
debug://screen-name?param=value # 带参数的导航
debug://state-name # 状态配置
使用 #if DEBUG 确保代码从发布构建中剥离。
# 启动模拟器
xcrun simctl boot "iPhone 16 Pro"
# 启动应用
xcrun simctl launch booted com.example.YourApp
# 测试每个深度链接
xcrun simctl openurl booted "debug://settings"
xcrun simctl openurl booted "debug://profile?id=123"
#if DEBUG
func handleDebugURL(_ url: URL) {
if url.host == "settings" {
// ❌ 错误 — 创建了紧耦合
self.showingSettings = true
}
}
#endif
问题:URL 处理器现在拥有导航逻辑,重复了协调器/路由器模式。
✅ 正确 — 使用现有的导航系统:
#if DEBUG
func handleDebugURL(_ url: URL) {
if url.host == "settings" {
// 使用现有的 NavigationPath
path.append(Destination.settings)
}
}
#endif
// ❌ 错误 — 没有 #if DEBUG
func handleDebugURL(_ url: URL) {
// 这会发布给用户!
}
问题:调试端点暴露在生产环境中。存在安全风险。
✅ 正确 — 用 #if DEBUG 包装:
#if DEBUG
func handleDebugURL(_ url: URL) {
// 从发布构建中剥离
}
#endif
#if DEBUG
case "profile":
let userID = Int(url.queryItems?["id"] ?? "0")! // ❌ 强制解包
path.append(Destination.profile(id: userID))
#endif
问题:如果 id 缺失或无效,会导致崩溃。
✅ 正确 — 验证参数:
#if DEBUG
case "profile":
guard let idString = url.queryItems?["id"],
let userID = Int(idString) else {
print("⚠️ Invalid profile ID")
return
}
path.append(Destination.profile(id: userID))
#endif
在自动化工作流中使用调试深度链接之前:
#if DEBUG 包装/axiom:screenshot 命令配合使用simulator-tester 代理配合使用场景:您正在调试食谱应用编辑器屏幕中的布局问题。
之前(手动测试):
之后(使用调试深度链接):
xcrun simctl openurl booted "debug://recipe-edit?id=42"/axiom:screenshot节省时间:迭代速度提高 60-75%,并带有视觉验证
添加调试 URL 处理器,将内容追加到现有的 NavigationPath:
router.path.append(Destination.fromDebugURL(url))
从调试 URL 处理器触发协调器方法:
coordinator.navigate(to: .fromDebugURL(url))
与路由器的导航 API 集成:
AppRouter.shared.push(Screen.fromDebugURL(url))
关键原则:调试深度链接应该使用现有导航,而不是替换它。
#if DEBUG
case "test-scenario":
// 从 URL 解析复杂的测试场景
// 示例:debug://test-scenario?user=premium&recipes=empty&network=slow
if let userType = url.queryItems?["user"] {
configureUser(type: userType) // "premium", "free", "trial"
}
if let recipesState = url.queryItems?["recipes"] {
configureRecipes(state: recipesState) // "empty", "full", "error"
}
if let networkState = url.queryItems?["network"] {
configureNetwork(state: networkState) // "fast", "slow", "offline"
}
// 现在导航
path.append(Destination.recipes)
#endif
用法:
# 测试具有空食谱列表的高级用户
xcrun simctl openurl booted "debug://test-scenario?user=premium&recipes=empty"
# 测试具有错误处理的慢速网络
xcrun simctl openurl booted "debug://test-scenario?network=slow&recipes=error"
创建单个 URL 来设置并捕获状态:
#if DEBUG
case "screenshot":
// 解析屏幕和配置
guard let screen = url.queryItems?["screen"] else { return }
// 配置状态
if let state = url.queryItems?["state"] {
applyState(state)
}
// 导航
navigate(to: screen)
// 为外部捕获发布通知
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
NotificationCenter.default.post(
name: .readyForScreenshot,
object: screen
)
}
#endif
用法:
# 导航到具有错误状态的登录屏幕,等待,然后截图
xcrun simctl openurl booted "debug://screenshot?screen=login&state=error"
sleep 2
xcrun simctl io booted screenshot login-error.png
axiom-swiftui-nav — 生产环境深度链接和 NavigationStack 模式simulator-tester — 使用调试深度链接进行自动化模拟器测试axiom-xcode-debugging — 环境优先的调试工作流调试深度链接支持:
请记住:
#if DEBUG 包装所有调试代码每周安装次数
92
仓库
GitHub 星标数
601
首次出现
2026 年 1 月 21 日
安全审计
安装于
opencode78
codex72
gemini-cli71
claude-code71
cursor70
github-copilot68
Use when:
simulator-tester agent or /axiom:screenshotDo NOT use for :
axiom-swiftui-nav skill instead)→ Add debug-only URL scheme to enable xcrun simctl openurl navigation
→ Create debug deep links for each screen, callable from simulator
→ Add debug links that navigate AND configure state
If you're experiencing ANY of these, add debug deep links:
Testing friction :
Debugging inefficiency :
Solution : Add debug deep links that let you (and Claude Code) jump directly to any screen with any state configuration.
Add a debug-only URL scheme that routes to screens.
import SwiftUI
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
#if DEBUG
.onOpenURL { url in
handleDebugURL(url)
}
#endif
}
}
#if DEBUG
private func handleDebugURL(_ url: URL) {
guard url.scheme == "debug" else { return }
// Route based on host
switch url.host {
case "settings":
// Navigate to settings
NotificationCenter.default.post(
name: .navigateToSettings,
object: nil
)
case "profile":
// Navigate to profile
let userID = url.queryItems?["id"] ?? "current"
NotificationCenter.default.post(
name: .navigateToProfile,
object: userID
)
case "reset":
// Reset app to initial state
resetApp()
default:
print("⚠️ Unknown debug URL: \(url)")
}
}
#endif
}
#if DEBUG
extension Notification.Name {
static let navigateToSettings = Notification.Name("navigateToSettings")
static let navigateToProfile = Notification.Name("navigateToProfile")
}
extension URL {
var queryItems: [String: String]? {
guard let components = URLComponents(url: self, resolvingAgainstBaseURL: false),
let items = components.queryItems else {
return nil
}
return Dictionary(uniqueKeysWithValues: items.map { ($0.name, $0.value ?? "") })
}
}
#endif
Usage :
# From simulator
xcrun simctl openurl booted "debug://settings"
xcrun simctl openurl booted "debug://profile?id=123"
xcrun simctl openurl booted "debug://reset"
Integrate debug deep links with NavigationStack for robust navigation.
import SwiftUI
@MainActor
class DebugRouter: ObservableObject {
@Published var path = NavigationPath()
#if DEBUG
func handleDebugURL(_ url: URL) {
guard url.scheme == "debug" else { return }
switch url.host {
case "settings":
path.append(Destination.settings)
case "recipe":
if let id = url.queryItems?["id"], let recipeID = Int(id) {
path.append(Destination.recipe(id: recipeID))
}
case "recipe-edit":
if let id = url.queryItems?["id"], let recipeID = Int(id) {
// Navigate to recipe, then to edit
path.append(Destination.recipe(id: recipeID))
path.append(Destination.recipeEdit(id: recipeID))
}
case "reset":
path = NavigationPath() // Pop to root
default:
print("⚠️ Unknown debug URL: \(url)")
}
}
#endif
}
struct ContentView: View {
@StateObject private var router = DebugRouter()
var body: some View {
NavigationStack(path: $router.path) {
HomeView()
.navigationDestination(for: Destination.self) { destination in
destinationView(for: destination)
}
}
#if DEBUG
.onOpenURL { url in
router.handleDebugURL(url)
}
#endif
}
@ViewBuilder
private func destinationView(for destination: Destination) -> some View {
switch destination {
case .settings:
SettingsView()
case .recipe(let id):
RecipeDetailView(recipeID: id)
case .recipeEdit(let id):
RecipeEditView(recipeID: id)
}
}
}
enum Destination: Hashable {
case settings
case recipe(id: Int)
case recipeEdit(id: Int)
}
Usage :
# Navigate to settings
xcrun simctl openurl booted "debug://settings"
# Navigate to recipe #42
xcrun simctl openurl booted "debug://recipe?id=42"
# Navigate to recipe #42 edit screen
xcrun simctl openurl booted "debug://recipe-edit?id=42"
# Pop to root
xcrun simctl openurl booted "debug://reset"
Debug links that both navigate AND configure state.
#if DEBUG
extension DebugRouter {
func handleDebugURL(_ url: URL) {
guard url.scheme == "debug" else { return }
switch url.host {
case "login":
// Show login screen
path.append(Destination.login)
case "login-error":
// Show login screen WITH error state
path.append(Destination.login)
// Trigger error state
NotificationCenter.default.post(
name: .showLoginError,
object: "Invalid credentials"
)
case "recipe-empty":
// Show recipe list in empty state
UserDefaults.standard.set(true, forKey: "debug_emptyRecipeList")
path.append(Destination.recipes)
case "recipe-error":
// Show recipe list with network error
UserDefaults.standard.set(true, forKey: "debug_networkError")
path.append(Destination.recipes)
default:
print("⚠️ Unknown debug URL: \(url)")
}
}
}
#endif
Usage :
# Test login error state
xcrun simctl openurl booted "debug://login-error"
# Test empty recipe list
xcrun simctl openurl booted "debug://recipe-empty"
# Test network error handling
xcrun simctl openurl booted "debug://recipe-error"
Register the debug URL scheme ONLY in debug builds.
Step 1 : Add scheme to Info.plist
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>debug</string>
</array>
<key>CFBundleURLName</key>
<string>com.example.debug</string>
</dict>
</array>
Step 2 : Strip from release builds
Add a Run Script phase to your target's Build Phases (runs BEFORE "Copy Bundle Resources"):
# Strip debug URL scheme from Release builds
if [ "${CONFIGURATION}" = "Release" ]; then
echo "Removing debug URL scheme from Info.plist"
/usr/libexec/PlistBuddy -c "Delete :CFBundleURLTypes:0" "${BUILT_PRODUCTS_DIR}/${INFOPLIST_PATH}" 2>/dev/null || true
fi
Alternative : Use separate Info.plist files for Debug vs Release configurations in Build Settings.
/axiom:screenshot Command# 1. Navigate to screen
xcrun simctl openurl booted "debug://settings"
# 2. Wait for navigation
sleep 1
# 3. Capture screenshot
/axiom:screenshot
simulator-tester AgentSimply tell the agent:
The agent will use your debug deep links to navigate.
ALWAYS complete these steps before adding debug deep links:
List all screens you need to reach for testing:
- Settings screen
- Profile screen (with specific user ID)
- Recipe detail (with specific recipe ID)
- Error states (login error, network error, etc.)
- Empty states (no recipes, no favorites)
debug://screen-name # Simple screen navigation
debug://screen-name?param=value # Navigation with parameters
debug://state-name # State configuration
Use #if DEBUG to ensure code is stripped from release builds.
# Boot simulator
xcrun simctl boot "iPhone 16 Pro"
# Launch app
xcrun simctl launch booted com.example.YourApp
# Test each deep link
xcrun simctl openurl booted "debug://settings"
xcrun simctl openurl booted "debug://profile?id=123"
#if DEBUG
func handleDebugURL(_ url: URL) {
if url.host == "settings" {
// ❌ WRONG — Creates tight coupling
self.showingSettings = true
}
}
#endif
Problem : URL handler now owns navigation logic, duplicating coordinator/router patterns.
✅ RIGHT — Use existing navigation system :
#if DEBUG
func handleDebugURL(_ url: URL) {
if url.host == "settings" {
// Use existing NavigationPath
path.append(Destination.settings)
}
}
#endif
// ❌ WRONG — No #if DEBUG
func handleDebugURL(_ url: URL) {
// This ships to users!
}
Problem : Debug endpoints exposed in production. Security risk.
✅ RIGHT — Wrap in #if DEBUG :
#if DEBUG
func handleDebugURL(_ url: URL) {
// Stripped from release builds
}
#endif
#if DEBUG
case "profile":
let userID = Int(url.queryItems?["id"] ?? "0")! // ❌ Force unwrap
path.append(Destination.profile(id: userID))
#endif
Problem : Crashes if id is missing or invalid.
✅ RIGHT — Validate parameters :
#if DEBUG
case "profile":
guard let idString = url.queryItems?["id"],
let userID = Int(idString) else {
print("⚠️ Invalid profile ID")
return
}
path.append(Destination.profile(id: userID))
#endif
Before using debug deep links in automated workflows:
#if DEBUG/axiom:screenshot commandsimulator-tester agentScenario : You're debugging a recipe app layout issue in the editor screen.
Before (manual testing):
After (with debug deep links):
xcrun simctl openurl booted "debug://recipe-edit?id=42"/axiom:screenshotTime savings : 60-75% faster iteration with visual verification
Add debug URL handler that appends to existing NavigationPath:
router.path.append(Destination.fromDebugURL(url))
Trigger coordinator methods from debug URL handler:
coordinator.navigate(to: .fromDebugURL(url))
Integrate with your router's navigation API:
AppRouter.shared.push(Screen.fromDebugURL(url))
Key principle : Debug deep links should USE existing navigation, not replace it.
#if DEBUG
case "test-scenario":
// Parse complex test scenario from URL
// Example: debug://test-scenario?user=premium&recipes=empty&network=slow
if let userType = url.queryItems?["user"] {
configureUser(type: userType) // "premium", "free", "trial"
}
if let recipesState = url.queryItems?["recipes"] {
configureRecipes(state: recipesState) // "empty", "full", "error"
}
if let networkState = url.queryItems?["network"] {
configureNetwork(state: networkState) // "fast", "slow", "offline"
}
// Now navigate
path.append(Destination.recipes)
#endif
Usage :
# Test premium user with empty recipe list
xcrun simctl openurl booted "debug://test-scenario?user=premium&recipes=empty"
# Test slow network with error handling
xcrun simctl openurl booted "debug://test-scenario?network=slow&recipes=error"
Create a single URL that sets up AND captures state:
#if DEBUG
case "screenshot":
// Parse screen and configuration
guard let screen = url.queryItems?["screen"] else { return }
// Configure state
if let state = url.queryItems?["state"] {
applyState(state)
}
// Navigate
navigate(to: screen)
// Post notification for external capture
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
NotificationCenter.default.post(
name: .readyForScreenshot,
object: screen
)
}
#endif
Usage :
# Navigate to login screen with error state, wait, then screenshot
xcrun simctl openurl booted "debug://screenshot?screen=login&state=error"
sleep 2
xcrun simctl io booted screenshot login-error.png
axiom-swiftui-nav — Production deep linking and NavigationStack patternssimulator-tester — Automated simulator testing using debug deep linksaxiom-xcode-debugging — Environment-first debugging workflowsDebug deep links enable:
Remember :
#if DEBUGWeekly Installs
92
Repository
GitHub Stars
601
First Seen
Jan 21, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
opencode78
codex72
gemini-cli71
claude-code71
cursor70
github-copilot68
通过 LiteLLM 代理让 Claude Code 对接 GitHub Copilot 运行 | 高级变通方案指南
36,300 周安装