import Foundation /// 主窗口「新建项目」表单背后的命令模型。 /// /// 真正干活的是 `scripts/new-project.sh`(和 /new-project skill、命令行共用 /// 同一份逻辑)—— app 只负责收集参数、拼命令、展示输出。建仓逻辑只维护一份。 public struct NewProjectCommand: Equatable, Sendable { public enum Remote: String, CaseIterable, Identifiable, Sendable { case privateRepo case publicRepo case localOnly public var id: String { rawValue } public var label: String { switch self { case .privateRepo: "GitHub 私有仓" case .publicRepo: "GitHub 公开仓" case .localOnly: "只建本地仓" } } } public var name: String = "" public var parentDir: String = "~/Documents/code" public var remote: Remote = .privateRepo /// 防污染 deny(禁止项目会话写宿主机 ~/.claude/ 的全局面)。 public var applyDenyGuard: Bool = true public var descriptionText: String = "" public init() {} public var trimmedName: String { name.trimmingCharacters(in: .whitespacesAndNewlines) } /// 提交前校验。nil = 可以提交。 public var validationError: String? { let value = trimmedName if value.isEmpty { return "项目名不能为空" } if value.contains(where: { $0 == "/" || $0 == "\\" || $0.isWhitespace }) { return "项目名不能含空格或斜杠" } if value.hasPrefix("-") { return "项目名不能以 - 开头" } if parentDir.trimmingCharacters(in: .whitespaces).isEmpty { return "父目录不能为空" } return nil } /// 目标路径(展示用)。 public var targetPath: String { let parent = NSString(string: parentDir).expandingTildeInPath return "\(parent)/\(trimmedName)" } /// 拼给 `/bin/bash