chore: initial commit (Phase 3 scaffold)

This commit is contained in:
CxAI Ops 2026-05-16 10:52:04 -05:00
commit 6af426e57c
42 changed files with 3573 additions and 0 deletions

15
.editorconfig Normal file
View File

@ -0,0 +1,15 @@
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 4
[*.{yml,yaml,json,md}]
indent_size = 2
[Makefile]
indent_style = tab

36
.gitea/workflows/ci.yml Normal file
View File

@ -0,0 +1,36 @@
name: ci
on:
push:
branches: [main, master]
pull_request: {}
workflow_dispatch: {}
# We run a Linux runner; macOS xcodebuild is not available here.
# This workflow validates structure (yaml, file presence) and basic Swift.
jobs:
validate:
runs-on: cxai-hostinger
container: debian:bookworm
steps:
- name: Install deps
run: |
apt-get update -qq
apt-get install -y --no-install-recommends git ca-certificates curl python3-yaml >/dev/null
- uses: actions/checkout@v4
- name: Lint workflow YAML
run: |
python3 - <<'PY'
import sys, yaml, glob
for f in glob.glob('.gitea/workflows/*.yml') + glob.glob('.github/workflows/*.yml'):
try:
yaml.safe_load(open(f))
except Exception as e:
print(f'YAML ERROR {f}: {e}'); sys.exit(1)
print('workflows ok')
PY
- name: Project sanity
run: |
test -f README.md || (echo "missing README" && exit 1)
test -f Makefile || (echo "missing Makefile" && exit 1)
ls *.xcodeproj >/dev/null 2>&1 && echo "xcodeproj: $(ls -d *.xcodeproj)" || true
[ -f Package.swift ] && echo "Package.swift present" || true

27
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
name: build (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-14]
steps:
- uses: actions/checkout@v4
- name: Show toolchain
run: |
xcodebuild -version || true
swift --version || true
- name: Build
run: make build
- name: Test
run: make test
- name: Lint
run: make lint

54
.gitignore vendored Normal file
View File

@ -0,0 +1,54 @@
# macOS
.DS_Store
.AppleDouble
.LSOverride
Icon?
# Xcode
build/
DerivedData/
*.xcworkspace/xcuserdata/
*.xcodeproj/xcuserdata/
*.xcodeproj/project.xcworkspace/xcuserdata/
*.xcuserstate
*.moved-aside
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
# SwiftPM
.build/
.swiftpm/xcode/
.swiftpm/configuration/
Package.resolved
# CocoaPods / Carthage
Pods/
Carthage/Build/
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output/
# Coverage
*.gcno
*.gcda
*.profdata
*.profraw
coverage/
# Editors
.vscode/
.idea/
*.swp
# Local
*.local
secrets.env

16
CHANGELOG.md Normal file
View File

@ -0,0 +1,16 @@
# Changelog
All notable changes to this project will be documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
and this project adheres to [Semantic Versioning](https://semver.org/).
## [Unreleased]
### Added
- Repository scaffolding: README, LICENSE (MIT), .gitignore, Makefile,
CONTRIBUTING, SECURITY, CODEOWNERS, .editorconfig, CI workflow.
## [0.1.0] - 2026-04-22
### Added
- Initial app-ios scaffold for CxLLM-IOS.

6
CODEOWNERS Normal file
View File

@ -0,0 +1,6 @@
# Default owners for everything in this repo.
* @CxAI-LLM/maintainers
# Build & CI
/.github/ @CxAI-LLM/devops
/Makefile @CxAI-LLM/devops

23
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,23 @@
# Contributing to CxLLM-IOS
Thanks for taking the time to contribute!
## Workflow
1. Open an issue describing the change before sending a PR for non-trivial work.
2. Fork / branch off `main`. Use a descriptive branch name (`feat/...`, `fix/...`).
3. Keep commits scoped and use **Conventional Commits** (`feat:`, `fix:`,
`docs:`, `refactor:`, `test:`, `chore:`).
4. Run `make lint` and `make test` before pushing.
5. Open a PR — CI must pass before review.
## Coding style
- Swift: `swiftformat` defaults + `swiftlint` rules from the umbrella repo.
- Objective-C / C / C++: clang-format `-style=Google`.
- No tabs in Swift; 4-space indent in C/C++.
## Code of conduct
By contributing you agree to abide by the project's Code of Conduct
(see the umbrella `cxllm-code` repo).

View File

@ -0,0 +1,600 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 77;
objects = {
/* Begin PBXContainerItemProxy section */
20F2AF332F99700100E7D2D9 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 20F2AF192F99700000E7D2D9 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 20F2AF202F99700000E7D2D9;
remoteInfo = "CxLLM-IOS";
};
20F2AF3D2F99700100E7D2D9 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 20F2AF192F99700000E7D2D9 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 20F2AF202F99700000E7D2D9;
remoteInfo = "CxLLM-IOS";
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
20F2AF212F99700000E7D2D9 /* CxLLM-IOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "CxLLM-IOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
20F2AF322F99700100E7D2D9 /* CxLLM-IOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CxLLM-IOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
20F2AF3C2F99700100E7D2D9 /* CxLLM-IOSUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "CxLLM-IOSUITests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
20F2AF442F99700100E7D2D9 /* Exceptions for "CxLLM-IOS" folder in "CxLLM-IOS" target */ = {
isa = PBXFileSystemSynchronizedBuildFileExceptionSet;
membershipExceptions = (
Info.plist,
);
target = 20F2AF202F99700000E7D2D9 /* CxLLM-IOS */;
};
/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */
/* Begin PBXFileSystemSynchronizedRootGroup section */
20F2AF232F99700000E7D2D9 /* CxLLM-IOS */ = {
isa = PBXFileSystemSynchronizedRootGroup;
exceptions = (
20F2AF442F99700100E7D2D9 /* Exceptions for "CxLLM-IOS" folder in "CxLLM-IOS" target */,
);
path = "CxLLM-IOS";
sourceTree = "<group>";
};
20F2AF352F99700100E7D2D9 /* CxLLM-IOSTests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = "CxLLM-IOSTests";
sourceTree = "<group>";
};
20F2AF3F2F99700100E7D2D9 /* CxLLM-IOSUITests */ = {
isa = PBXFileSystemSynchronizedRootGroup;
path = "CxLLM-IOSUITests";
sourceTree = "<group>";
};
/* End PBXFileSystemSynchronizedRootGroup section */
/* Begin PBXFrameworksBuildPhase section */
20F2AF1E2F99700000E7D2D9 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
20F2AF2F2F99700100E7D2D9 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
20F2AF392F99700100E7D2D9 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
20F2AF182F99700000E7D2D9 = {
isa = PBXGroup;
children = (
20F2AF232F99700000E7D2D9 /* CxLLM-IOS */,
20F2AF352F99700100E7D2D9 /* CxLLM-IOSTests */,
20F2AF3F2F99700100E7D2D9 /* CxLLM-IOSUITests */,
20F2AF222F99700000E7D2D9 /* Products */,
);
sourceTree = "<group>";
};
20F2AF222F99700000E7D2D9 /* Products */ = {
isa = PBXGroup;
children = (
20F2AF212F99700000E7D2D9 /* CxLLM-IOS.app */,
20F2AF322F99700100E7D2D9 /* CxLLM-IOSTests.xctest */,
20F2AF3C2F99700100E7D2D9 /* CxLLM-IOSUITests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
20F2AF202F99700000E7D2D9 /* CxLLM-IOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 20F2AF452F99700100E7D2D9 /* Build configuration list for PBXNativeTarget "CxLLM-IOS" */;
buildPhases = (
20F2AF1D2F99700000E7D2D9 /* Sources */,
20F2AF1E2F99700000E7D2D9 /* Frameworks */,
20F2AF1F2F99700000E7D2D9 /* Resources */,
);
buildRules = (
);
dependencies = (
);
fileSystemSynchronizedGroups = (
20F2AF232F99700000E7D2D9 /* CxLLM-IOS */,
);
name = "CxLLM-IOS";
packageProductDependencies = (
);
productName = "CxLLM-IOS";
productReference = 20F2AF212F99700000E7D2D9 /* CxLLM-IOS.app */;
productType = "com.apple.product-type.application";
};
20F2AF312F99700100E7D2D9 /* CxLLM-IOSTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 20F2AF4A2F99700100E7D2D9 /* Build configuration list for PBXNativeTarget "CxLLM-IOSTests" */;
buildPhases = (
20F2AF2E2F99700100E7D2D9 /* Sources */,
20F2AF2F2F99700100E7D2D9 /* Frameworks */,
20F2AF302F99700100E7D2D9 /* Resources */,
);
buildRules = (
);
dependencies = (
20F2AF342F99700100E7D2D9 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
20F2AF352F99700100E7D2D9 /* CxLLM-IOSTests */,
);
name = "CxLLM-IOSTests";
packageProductDependencies = (
);
productName = "CxLLM-IOSTests";
productReference = 20F2AF322F99700100E7D2D9 /* CxLLM-IOSTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
20F2AF3B2F99700100E7D2D9 /* CxLLM-IOSUITests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 20F2AF4D2F99700100E7D2D9 /* Build configuration list for PBXNativeTarget "CxLLM-IOSUITests" */;
buildPhases = (
20F2AF382F99700100E7D2D9 /* Sources */,
20F2AF392F99700100E7D2D9 /* Frameworks */,
20F2AF3A2F99700100E7D2D9 /* Resources */,
);
buildRules = (
);
dependencies = (
20F2AF3E2F99700100E7D2D9 /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
20F2AF3F2F99700100E7D2D9 /* CxLLM-IOSUITests */,
);
name = "CxLLM-IOSUITests";
packageProductDependencies = (
);
productName = "CxLLM-IOSUITests";
productReference = 20F2AF3C2F99700100E7D2D9 /* CxLLM-IOSUITests.xctest */;
productType = "com.apple.product-type.bundle.ui-testing";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
20F2AF192F99700000E7D2D9 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 2630;
LastUpgradeCheck = 2630;
TargetAttributes = {
20F2AF202F99700000E7D2D9 = {
CreatedOnToolsVersion = 26.3;
};
20F2AF312F99700100E7D2D9 = {
CreatedOnToolsVersion = 26.3;
TestTargetID = 20F2AF202F99700000E7D2D9;
};
20F2AF3B2F99700100E7D2D9 = {
CreatedOnToolsVersion = 26.3;
TestTargetID = 20F2AF202F99700000E7D2D9;
};
};
};
buildConfigurationList = 20F2AF1C2F99700000E7D2D9 /* Build configuration list for PBXProject "CxLLM-IOS" */;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 20F2AF182F99700000E7D2D9;
minimizedProjectReferenceProxies = 1;
preferredProjectObjectVersion = 77;
productRefGroup = 20F2AF222F99700000E7D2D9 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
20F2AF202F99700000E7D2D9 /* CxLLM-IOS */,
20F2AF312F99700100E7D2D9 /* CxLLM-IOSTests */,
20F2AF3B2F99700100E7D2D9 /* CxLLM-IOSUITests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
20F2AF1F2F99700000E7D2D9 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
20F2AF302F99700100E7D2D9 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
20F2AF3A2F99700100E7D2D9 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
20F2AF1D2F99700000E7D2D9 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
20F2AF2E2F99700100E7D2D9 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
20F2AF382F99700100E7D2D9 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
20F2AF342F99700100E7D2D9 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 20F2AF202F99700000E7D2D9 /* CxLLM-IOS */;
targetProxy = 20F2AF332F99700100E7D2D9 /* PBXContainerItemProxy */;
};
20F2AF3E2F99700100E7D2D9 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 20F2AF202F99700000E7D2D9 /* CxLLM-IOS */;
targetProxy = 20F2AF3D2F99700100E7D2D9 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
20F2AF462F99700100E7D2D9 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "CxLLM-IOS/CxLLM_IOS.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = DKWVC9FQJY;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "CxLLM-IOS/Info.plist";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "cxai-studio.CxLLM-IOS";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
20F2AF472F99700100E7D2D9 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = "CxLLM-IOS/CxLLM_IOS.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = DKWVC9FQJY;
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = "CxLLM-IOS/Info.plist";
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "cxai-studio.CxLLM-IOS";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
};
20F2AF482F99700100E7D2D9 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = DKWVC9FQJY;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
20F2AF492F99700100E7D2D9 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = DKWVC9FQJY;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.2;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
20F2AF4B2F99700100E7D2D9 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = DKWVC9FQJY;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "cxai-studio.CxLLM-IOSTests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CxLLM-IOS.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CxLLM-IOS";
};
name = Debug;
};
20F2AF4C2F99700100E7D2D9 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = DKWVC9FQJY;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 26.2;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "cxai-studio.CxLLM-IOSTests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CxLLM-IOS.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/CxLLM-IOS";
};
name = Release;
};
20F2AF4E2F99700100E7D2D9 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = DKWVC9FQJY;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "cxai-studio.CxLLM-IOSUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = "CxLLM-IOS";
};
name = Debug;
};
20F2AF4F2F99700100E7D2D9 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = DKWVC9FQJY;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = "cxai-studio.CxLLM-IOSUITests";
PRODUCT_NAME = "$(TARGET_NAME)";
STRING_CATALOG_GENERATE_SYMBOLS = NO;
SWIFT_APPROACHABLE_CONCURRENCY = YES;
SWIFT_EMIT_LOC_STRINGS = NO;
SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
TEST_TARGET_NAME = "CxLLM-IOS";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
20F2AF1C2F99700000E7D2D9 /* Build configuration list for PBXProject "CxLLM-IOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
20F2AF482F99700100E7D2D9 /* Debug */,
20F2AF492F99700100E7D2D9 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
20F2AF452F99700100E7D2D9 /* Build configuration list for PBXNativeTarget "CxLLM-IOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
20F2AF462F99700100E7D2D9 /* Debug */,
20F2AF472F99700100E7D2D9 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
20F2AF4A2F99700100E7D2D9 /* Build configuration list for PBXNativeTarget "CxLLM-IOSTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
20F2AF4B2F99700100E7D2D9 /* Debug */,
20F2AF4C2F99700100E7D2D9 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
20F2AF4D2F99700100E7D2D9 /* Build configuration list for PBXNativeTarget "CxLLM-IOSUITests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
20F2AF4E2F99700100E7D2D9 /* Debug */,
20F2AF4F2F99700100E7D2D9 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 20F2AF192F99700000E7D2D9 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,35 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "tinted"
}
],
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,61 @@
//
// ContentView.swift
// CxLLM-IOS
//
// Created by Stephen Carter on 4/22/26.
//
import SwiftUI
import SwiftData
struct ContentView: View {
@Environment(\.modelContext) private var modelContext
@Query private var items: [Item]
var body: some View {
NavigationSplitView {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))")
} label: {
Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
} detail: {
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = Item(timestamp: Date())
modelContext.insert(newItem)
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
for index in offsets {
modelContext.delete(items[index])
}
}
}
}
#Preview {
ContentView()
.modelContainer(for: Item.self, inMemory: true)
}

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.icloud-container-identifiers</key>
<array/>
<key>com.apple.developer.icloud-services</key>
<array>
<string>CloudKit</string>
</array>
</dict>
</plist>

View File

@ -0,0 +1,32 @@
//
// CxLLM_IOSApp.swift
// CxLLM-IOS
//
// Created by Stephen Carter on 4/22/26.
//
import SwiftUI
import SwiftData
@main
struct CxLLM_IOSApp: App {
var sharedModelContainer: ModelContainer = {
let schema = Schema([
Item.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()
var body: some Scene {
WindowGroup {
ContentView()
}
.modelContainer(sharedModelContainer)
}
}

10
CxLLM-IOS/Info.plist Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
</array>
</dict>
</plist>

18
CxLLM-IOS/Item.swift Normal file
View File

@ -0,0 +1,18 @@
//
// Item.swift
// CxLLM-IOS
//
// Created by Stephen Carter on 4/22/26.
//
import Foundation
import SwiftData
@Model
final class Item {
var timestamp: Date
init(timestamp: Date) {
self.timestamp = timestamp
}
}

View File

@ -0,0 +1,17 @@
//
// CxLLM_IOSTests.swift
// CxLLM-IOSTests
//
// Created by Stephen Carter on 4/22/26.
//
import Testing
@testable import CxLLM_IOS
struct CxLLM_IOSTests {
@Test func example() async throws {
// Write your test here and use APIs like `#expect(...)` to check expected conditions.
}
}

View File

@ -0,0 +1,41 @@
//
// CxLLM_IOSUITests.swift
// CxLLM-IOSUITests
//
// Created by Stephen Carter on 4/22/26.
//
import XCTest
final class CxLLM_IOSUITests: XCTestCase {
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
// In UI tests it is usually best to stop immediately when a failure occurs.
continueAfterFailure = false
// In UI tests its important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
@MainActor
func testExample() throws {
// UI tests must launch the application that they test.
let app = XCUIApplication()
app.launch()
// Use XCTAssert and related functions to verify your tests produce the correct results.
}
@MainActor
func testLaunchPerformance() throws {
// This measures how long it takes to launch your application.
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
}

View File

@ -0,0 +1,33 @@
//
// CxLLM_IOSUITestsLaunchTests.swift
// CxLLM-IOSUITests
//
// Created by Stephen Carter on 4/22/26.
//
import XCTest
final class CxLLM_IOSUITestsLaunchTests: XCTestCase {
override class var runsForEachTargetApplicationUIConfiguration: Bool {
true
}
override func setUpWithError() throws {
continueAfterFailure = false
}
@MainActor
func testLaunch() throws {
let app = XCUIApplication()
app.launch()
// Insert steps here to perform after app launch but before taking a screenshot,
// such as logging into a test account or navigating somewhere in the app
let attachment = XCTAttachment(screenshot: app.screenshot())
attachment.name = "Launch Screen"
attachment.lifetime = .keepAlways
add(attachment)
}
}

View File

@ -0,0 +1,67 @@
// CxLLM Studio iOS Application
// ContentView.swift Tab-based navigation for iOS
import SwiftUI
import CxCode
import CxAWS
import CxGit
struct ContentView: View {
@Environment(AppState.self) private var appState
@Environment(GatewayService.self) private var gateway
var body: some View {
@Bindable var state = appState
TabView(selection: $state.selectedTab) {
Tab("Chat", systemImage: "bubble.left.and.bubble.right.fill", value: AppState.Tab.chat) {
NavigationStack {
ChatView()
.navigationTitle("Chat")
}
}
Tab("Models", systemImage: "cpu", value: AppState.Tab.models) {
NavigationStack {
ModelsView()
.navigationTitle("CxModels")
}
}
Tab("AWS", systemImage: "cloud.fill", value: AppState.Tab.aws) {
NavigationStack {
AWSView()
.navigationTitle("AWS DevOps")
}
}
Tab("Git", systemImage: "arrow.triangle.branch", value: AppState.Tab.git) {
NavigationStack {
GitView()
.navigationTitle("CxGit")
}
}
Tab("Agent", systemImage: "bolt.fill", value: AppState.Tab.agent) {
NavigationStack {
AgentView()
.navigationTitle("Agent")
}
}
Tab("MCP", systemImage: "network", value: AppState.Tab.mcp) {
NavigationStack {
MCPView()
.navigationTitle("MCP")
}
}
Tab("Settings", systemImage: "gear", value: AppState.Tab.settings) {
NavigationStack {
SettingsView()
.navigationTitle("Settings")
}
}
}
}
}

View File

@ -0,0 +1,50 @@
// CxLLM Studio iOS Application
// CxLLMStudioApp.swift Main app entry point
import SwiftUI
import CxCode
import CxAWS
import CxGit
@main
struct CxLLMStudioApp: App {
@State private var appState = AppState()
@State private var gatewayService: GatewayService
@State private var modelController: CxModelController
@State private var awsService: AWSService
@State private var gitService: GitService
@State private var agentService: AgentService
@State private var mcpService: MCPService
init() {
let config = CxConfig.fromEnvironment()
let gateway = CxGateway(config: config)
self._gatewayService = State(initialValue: GatewayService(gateway: gateway))
self._modelController = State(initialValue: CxModelController(gateway: gateway))
self._awsService = State(initialValue: AWSService(config: CxAWSConfig()))
self._gitService = State(initialValue: GitService())
self._agentService = State(initialValue: AgentService(gateway: gateway))
self._mcpService = State(initialValue: MCPService(gateway: gateway))
}
var body: some Scene {
WindowGroup {
ContentView()
.environment(appState)
.environment(gatewayService)
.environment(modelController)
.environment(awsService)
.environment(gitService)
.environment(agentService)
.environment(mcpService)
.task {
await gatewayService.checkHealth()
await modelController.loadModels()
await awsService.checkHealth()
await gitService.checkHealth()
await agentService.initialize()
await mcpService.initialize()
}
}
}
}

View File

@ -0,0 +1,95 @@
// CxLLM Studio macOS Application
// Services/AWSService.swift AWS DevOps integration service
import Foundation
import CxAWS
import CxCode
@Observable
final class AWSService {
private(set) var isConnected = false
private(set) var healthStatus: AWSHealthStatus?
private(set) var repositories: [[String: Any]] = []
private(set) var pipelines: [[String: Any]] = []
private(set) var pullRequests: [[String: Any]] = []
private(set) var builds: [[String: Any]] = []
private(set) var error: String?
let client: CxAWSClient
init(config: CxAWSConfig) {
self.client = CxAWSClient(config: config)
}
func checkHealth() async {
let health = await client.healthCheck()
healthStatus = health
isConnected = health.status == "ok"
}
func loadRepositories() async {
do {
let result = try await client.codecommitListRepositories()
repositories = result["repositories"] as? [[String: Any]] ?? []
error = nil
} catch {
self.error = error.localizedDescription
}
}
func loadPipelines() async {
do {
let result = try await client.codepipelineListPipelines()
pipelines = result["pipelines"] as? [[String: Any]] ?? []
error = nil
} catch {
self.error = error.localizedDescription
}
}
func loadPullRequests(repo: String) async {
do {
let result = try await client.codecommitListPullRequests(repo)
pullRequests = (result["pullRequestIds"] as? [String])?.map { ["id": $0] } ?? []
error = nil
} catch {
self.error = error.localizedDescription
}
}
func startPipeline(_ name: String) async throws {
_ = try await client.codepipelineStartExecution(name)
}
func stopPipeline(_ name: String, executionId: String) async throws {
_ = try await client.codepipelineStopExecution(name, executionId: executionId)
}
func startBuild(_ project: String) async throws -> [String: Any] {
try await client.codebuildStartBuild(project)
}
func getBuildStatus(_ ids: [String]) async throws -> [String: Any] {
try await client.codebuildBatchGetBuilds(ids)
}
func getFile(repo: String, path: String, ref: String = "") async throws -> [String: Any] {
try await client.codecommitGetFile(repo, path: path, ref: ref)
}
func getPipelineState(_ name: String) async throws -> [String: Any] {
try await client.codepipelineGetPipelineState(name)
}
func getLogGroups() async throws -> [String: Any] {
try await client.logsDescribeLogGroups(prefix: "/aws/codebuild/")
}
func getLogStreams(logGroup: String) async throws -> [String: Any] {
try await client.logsDescribeLogStreams(logGroup: logGroup)
}
func getLogEvents(logGroup: String, logStream: String) async throws -> [String: Any] {
try await client.logsGetLogEvents(logGroup: logGroup, logStream: logStream)
}
}

View File

@ -0,0 +1,181 @@
// CxLLM Studio iOS Application
// Services/AgentService.swift Agent operations service
import Foundation
import CxCode
@Observable
final class AgentService {
// MARK: - State
private(set) var isHealthy = false
private(set) var tools: [AgentToolInfo] = []
private(set) var isRunning = false
private(set) var isExecutingTool = false
private(set) var error: String?
private(set) var lastHealthCheck: Date?
private(set) var totalExecutions: Int = 0
private(set) var totalToolCalls: Int = 0
private(set) var history: [AgentExecution] = []
private let gateway: CxGateway
init(gateway: CxGateway) {
self.gateway = gateway
}
// MARK: - Health & Discovery
func checkHealth() async {
do {
let _ = try await gateway.agent.health()
isHealthy = true
error = nil
lastHealthCheck = Date()
} catch {
isHealthy = false
self.error = error.localizedDescription
lastHealthCheck = Date()
}
}
func loadTools() async {
do {
tools = try await gateway.agent.listTools()
error = nil
} catch {
self.error = error.localizedDescription
}
}
func initialize() async {
await checkHealth()
if isHealthy {
await loadTools()
}
}
// MARK: - Autopilot
func runAutopilot(task: String, maxSteps: Int = 10) async throws -> AgentExecution {
guard isHealthy else {
throw AgentServiceError.notHealthy
}
guard !isRunning else {
throw AgentServiceError.alreadyRunning
}
isRunning = true
error = nil
let startTime = Date()
do {
let result = try await gateway.agent.autoPilot(task: task, maxSteps: maxSteps)
let duration = Date().timeIntervalSince(startTime)
let execution = AgentExecution(
task: task,
steps: [],
output: result,
time: Date(),
duration: duration,
success: true
)
history.insert(execution, at: 0)
totalExecutions += 1
isRunning = false
return execution
} catch {
let duration = Date().timeIntervalSince(startTime)
let execution = AgentExecution(
task: task,
steps: [],
output: "Error: \(error.localizedDescription)",
time: Date(),
duration: duration,
success: false
)
history.insert(execution, at: 0)
self.error = error.localizedDescription
isRunning = false
throw error
}
}
// MARK: - Tool Execution
func callTool(name: String, arguments: [String: Any]) async throws -> String {
guard !isExecutingTool else {
throw AgentServiceError.alreadyRunning
}
isExecutingTool = true
error = nil
do {
let result = try await gateway.agent.callTool(name: name, arguments: arguments)
totalToolCalls += 1
isExecutingTool = false
return result
} catch {
self.error = error.localizedDescription
isExecutingTool = false
throw error
}
}
// MARK: - History Management
func clearHistory() {
history.removeAll()
}
// MARK: - Computed
var toolNames: [String] {
tools.map(\.name)
}
var successRate: Double {
guard !history.isEmpty else { return 0 }
let successes = history.filter(\.success).count
return Double(successes) / Double(history.count)
}
var averageDuration: TimeInterval {
guard !history.isEmpty else { return 0 }
return history.reduce(0.0) { $0 + $1.duration } / Double(history.count)
}
}
// MARK: - Supporting Types
struct AgentStep: Identifiable {
let id = UUID()
let index: Int
let action: String
let result: String
let timestamp: Date
}
struct AgentExecution: Identifiable {
let id = UUID()
let task: String
let steps: [AgentStep]
let output: String
let time: Date
let duration: TimeInterval
let success: Bool
}
enum AgentServiceError: LocalizedError {
case notHealthy
case alreadyRunning
var errorDescription: String? {
switch self {
case .notHealthy: return "Agent service is not healthy. Check connection."
case .alreadyRunning: return "An operation is already in progress."
}
}
}

View File

@ -0,0 +1,77 @@
// CxLLM Studio macOS Application
// Services/AppState.swift Global application state
import SwiftUI
import CxCode
import CxAWS
@Observable
final class AppState {
enum Tab: String, CaseIterable {
case chat, models, aws, git, agent, mcp, settings
}
var selectedTab: Tab = .chat
var conversations: [Conversation] = []
var activeConversationId: String?
var selectedModel: CxModelSlot = .spark
var activeConversation: Conversation? {
get { conversations.first { $0.id == activeConversationId } }
set {
if let newValue, let idx = conversations.firstIndex(where: { $0.id == newValue.id }) {
conversations[idx] = newValue
}
}
}
func createConversation() {
let conv = Conversation(model: selectedModel)
conversations.insert(conv, at: 0)
activeConversationId = conv.id
}
func deleteConversation(_ id: String) {
conversations.removeAll { $0.id == id }
if activeConversationId == id {
activeConversationId = conversations.first?.id
}
}
}
// MARK: - Data Models
struct Conversation: Identifiable, Codable {
var id: String = UUID().uuidString
var title: String = "New Chat"
var model: CxModelSlot
var messages: [Message] = []
var createdAt: Date = Date()
var isAgentMode: Bool = false
var sessionId: String?
var lastMessage: String? {
messages.last?.content
}
}
struct Message: Identifiable, Codable {
var id: String = UUID().uuidString
var role: Role
var content: String
var timestamp: Date = Date()
var model: String?
var isStreaming: Bool = false
enum Role: String, Codable {
case system, user, assistant, tool
}
static func user(_ content: String) -> Message {
Message(role: .user, content: content)
}
static func assistant(_ content: String, model: String? = nil) -> Message {
Message(role: .assistant, content: content, model: model)
}
}

View File

@ -0,0 +1,48 @@
// CxLLM Studio macOS Application
// Services/GatewayService.swift Gateway connection service
import Foundation
import CxCode
@Observable
final class GatewayService {
private(set) var isConnected = false
private(set) var healthStatus: String = "unknown"
private(set) var connectionError: String?
let gateway: CxGateway
init(gateway: CxGateway) {
self.gateway = gateway
}
func checkHealth() async {
do {
let health = try await gateway.health()
healthStatus = health.status
isConnected = health.status == "ok"
connectionError = nil
} catch {
isConnected = false
connectionError = error.localizedDescription
}
}
func createSession(model: String) async throws -> String {
let session = try await gateway.sessions.create(model: model)
return session.sessionId
}
func sendMessage(sessionId: String, message: String, model: String) async throws -> ChatResponse {
try await gateway.chat.ask(sessionId: sessionId, message: message, model: model)
}
func streamMessage(sessionId: String, message: String, model: String) -> AsyncThrowingStream<String, Error> {
gateway.chat.streamAsk(sessionId: sessionId, message: message, model: model)
}
func configure(baseURL: String, apiKey: String) {
// Recreate gateway with new config would go here
// For now, config is immutable after init
}
}

View File

@ -0,0 +1,102 @@
// CxLLM Studio macOS Application
// Services/GitService.swift Gitea integration service
import Foundation
import CxGit
import CxCode
@Observable
final class GitService {
private(set) var isConnected = false
private(set) var healthStatus: GitHealthStatus?
private(set) var repositories: [GitRepository] = []
private(set) var pullRequests: [GitPullRequest] = []
private(set) var issues: [GitIssue] = []
private(set) var branches: [GitBranch] = []
private(set) var actionRuns: [GitActionRun] = []
private(set) var currentUser: GitUser?
private(set) var error: String?
let client: CxGitClient
init(config: CxGitConfig = CxGitConfig()) {
self.client = CxGitClient(config: config)
}
func checkHealth() async {
let health = await client.healthCheck()
healthStatus = health
isConnected = health.isHealthy
}
func loadRepositories(org: String? = nil) async {
do {
repositories = try await client.listOrgRepos(org)
error = nil
} catch {
self.error = error.localizedDescription
}
}
func loadPullRequests(owner: String, repo: String) async {
do {
pullRequests = try await client.listPulls(owner, repo)
error = nil
} catch {
self.error = error.localizedDescription
}
}
func loadIssues(owner: String, repo: String) async {
do {
issues = try await client.listIssues(owner, repo)
error = nil
} catch {
self.error = error.localizedDescription
}
}
func loadBranches(owner: String, repo: String) async {
do {
branches = try await client.listBranches(owner, repo)
error = nil
} catch {
self.error = error.localizedDescription
}
}
func loadActionRuns(owner: String, repo: String) async {
do {
actionRuns = try await client.listActionRuns(owner, repo)
error = nil
} catch {
self.error = error.localizedDescription
}
}
func loadCurrentUser() async {
do {
currentUser = try await client.authenticatedUser()
} catch {
// Not critical user may not be authenticated
}
}
func refreshAll() async {
await checkHealth()
await loadRepositories()
await loadCurrentUser()
}
func createPullRequest(owner: String, repo: String, title: String, head: String, base: String = "main", body: String = "") async throws -> GitPullRequest {
try await client.createPull(owner, repo, title: title, head: head, base: base, body: body)
}
func createIssue(owner: String, repo: String, title: String, body: String = "") async throws -> GitIssue {
try await client.createIssue(owner, repo, title: title, body: body)
}
func createBranch(owner: String, repo: String, name: String, from: String = "main") async throws -> GitBranch {
try await client.createBranch(owner, repo, name: name, from: from)
}
}

View File

@ -0,0 +1,214 @@
// CxLLM Studio iOS Application
// Services/MCPService.swift Model Context Protocol service
import Foundation
import CxCode
@Observable
final class MCPService {
// MARK: - State
private(set) var isInitialized = false
private(set) var pingOk = false
private(set) var tools: [McpToolDef] = []
private(set) var resources: [McpResource] = []
private(set) var error: String?
private(set) var statusMessage: String = "Not initialized"
private(set) var isExecutingTool = false
private(set) var isReadingResource = false
private(set) var history: [MCPHistoryEntry] = []
private(set) var totalToolCalls: Int = 0
private(set) var totalResourceReads: Int = 0
private(set) var lastInitialized: Date?
private let gateway: CxGateway
init(gateway: CxGateway) {
self.gateway = gateway
}
// MARK: - Lifecycle
func initialize() async {
do {
_ = try await gateway.mcp.initialize()
isInitialized = true
error = nil
lastInitialized = Date()
if let _ = try? await gateway.mcp.ping() {
pingOk = true
}
tools = try await gateway.mcp.listTools()
resources = try await gateway.mcp.listResources()
statusMessage = "Ready — \(tools.count) tools, \(resources.count) resources"
} catch {
isInitialized = false
self.error = error.localizedDescription
statusMessage = "Error: \(error.localizedDescription)"
}
}
func ping() async -> Bool {
do {
let _ = try await gateway.mcp.ping()
pingOk = true
statusMessage = "Pong OK"
error = nil
return true
} catch {
pingOk = false
statusMessage = "Ping failed"
self.error = error.localizedDescription
return false
}
}
func reload() async {
do {
tools = try await gateway.mcp.listTools()
resources = try await gateway.mcp.listResources()
statusMessage = "Reloaded — \(tools.count) tools, \(resources.count) resources"
error = nil
} catch {
self.error = error.localizedDescription
statusMessage = "Reload failed: \(error.localizedDescription)"
}
}
// MARK: - Tool Execution
struct ToolCallResult {
let content: String
let isError: Bool
}
func callTool(name: String, arguments: [String: Any]) async -> ToolCallResult {
isExecutingTool = true
do {
let result = try await gateway.mcp.callTool(name: name, arguments: arguments)
let content = result.content?.compactMap(\.text).joined(separator: "\n") ?? "No content"
let isErr = result.isError == true
let displayContent = isErr ? "[ERROR] \(content)" : content
totalToolCalls += 1
history.insert(
MCPHistoryEntry(type: .tool, name: name, result: displayContent, time: Date(), success: !isErr),
at: 0
)
isExecutingTool = false
error = nil
return ToolCallResult(content: displayContent, isError: isErr)
} catch {
let errMsg = "Error: \(error.localizedDescription)"
history.insert(
MCPHistoryEntry(type: .tool, name: name, result: errMsg, time: Date(), success: false),
at: 0
)
self.error = error.localizedDescription
isExecutingTool = false
return ToolCallResult(content: errMsg, isError: true)
}
}
// MARK: - Resource Reading
func readResource(uri: String, name: String) async -> String {
isReadingResource = true
do {
let content = try await gateway.mcp.readResource(uri: uri)
totalResourceReads += 1
history.insert(
MCPHistoryEntry(type: .resource, name: name, result: String(content.prefix(100)), time: Date(), success: true),
at: 0
)
isReadingResource = false
error = nil
return content
} catch {
let errMsg = "Error: \(error.localizedDescription)"
history.insert(
MCPHistoryEntry(type: .resource, name: name, result: errMsg, time: Date(), success: false),
at: 0
)
self.error = error.localizedDescription
isReadingResource = false
return errMsg
}
}
// MARK: - History Management
func clearHistory() {
history.removeAll()
}
// MARK: - Computed
var connectionState: ConnectionState {
if !isInitialized { return .disconnected }
if pingOk { return .live }
return .initialized
}
var successRate: Double {
guard !history.isEmpty else { return 0 }
let successes = history.filter(\.success).count
return Double(successes) / Double(history.count)
}
func tool(named name: String) -> McpToolDef? {
tools.first { $0.name == name }
}
func filteredTools(query: String) -> [McpToolDef] {
guard !query.isEmpty else { return tools }
let q = query.lowercased()
return tools.filter {
$0.name.lowercased().contains(q) || ($0.description ?? "").lowercased().contains(q)
}
}
func filteredResources(query: String) -> [McpResource] {
guard !query.isEmpty else { return resources }
let q = query.lowercased()
return resources.filter {
$0.name.lowercased().contains(q) || $0.uri.lowercased().contains(q)
}
}
}
// MARK: - Supporting Types
enum ConnectionState {
case disconnected, initialized, live
var label: String {
switch self {
case .disconnected: return "Off"
case .initialized: return "Init"
case .live: return "Live"
}
}
var isConnected: Bool {
self != .disconnected
}
}
struct MCPHistoryEntry: Identifiable {
let id = UUID()
let type: EntryType
let name: String
let result: String
let time: Date
let success: Bool
enum EntryType: String {
case tool, resource
}
}

View File

@ -0,0 +1,132 @@
// CxLLM Studio iOS Application
// Views/AWS/AWSView.swift AWS DevOps dashboard for iOS
import SwiftUI
import CxAWS
struct AWSView: View {
@Environment(AWSService.self) private var aws
@State private var selectedSection: AWSSection = .overview
enum AWSSection: String, CaseIterable, Identifiable {
case overview, repos, pipelines, builds
var id: String { rawValue }
}
var body: some View {
List {
Picker("Section", selection: $selectedSection) {
ForEach(AWSSection.allCases) { s in
Text(s.rawValue.capitalized).tag(s)
}
}
.pickerStyle(.segmented)
.listRowBackground(Color.clear)
switch selectedSection {
case .overview: overviewSection
case .repos: reposSection
case .pipelines: pipelinesSection
case .builds: buildsSection
}
}
.task {
await aws.loadRepositories()
await aws.loadPipelines()
}
}
// MARK: - Overview
@ViewBuilder
private var overviewSection: some View {
Section("Status") {
LabeledContent("Connection") {
HStack {
Circle()
.fill(aws.isConnected ? .green : .red)
.frame(width: 8, height: 8)
Text(aws.isConnected ? "Connected" : "Offline")
}
}
LabeledContent("Region", value: aws.healthStatus?.region ?? "us-east-2")
LabeledContent("Repositories", value: "\(aws.repositories.count)")
LabeledContent("Pipelines", value: "\(aws.pipelines.count)")
}
if let error = aws.error {
Section("Error") {
Text(error)
.foregroundStyle(.red)
}
}
}
// MARK: - Repos
@ViewBuilder
private var reposSection: some View {
Section("CodeCommit Repositories") {
if aws.repositories.isEmpty {
ContentUnavailableView("No Repositories", systemImage: "chevron.left.forwardslash.chevron.right")
} else {
ForEach(aws.repositories.indices, id: \.self) { idx in
let repo = aws.repositories[idx]
VStack(alignment: .leading) {
Text(repo["repositoryName"] as? String ?? "Unknown")
.font(.headline)
Text(repo["repositoryId"] as? String ?? "")
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
}
// MARK: - Pipelines
@ViewBuilder
private var pipelinesSection: some View {
Section("CodePipeline") {
if aws.pipelines.isEmpty {
ContentUnavailableView("No Pipelines", systemImage: "arrow.triangle.branch")
} else {
ForEach(aws.pipelines.indices, id: \.self) { idx in
let pipeline = aws.pipelines[idx]
HStack {
VStack(alignment: .leading) {
Text(pipeline["name"] as? String ?? "Unknown")
.font(.headline)
Text("Version \(pipeline["version"] as? Int ?? 0)")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
Button("Start") {
Task {
try? await aws.startPipeline(pipeline["name"] as? String ?? "")
}
}
.buttonStyle(.bordered)
.tint(.green)
}
}
}
}
}
// MARK: - Builds
@ViewBuilder
private var buildsSection: some View {
Section("CodeBuild") {
Button {
Task { _ = try? await aws.startBuild("cxllm-studio-build") }
} label: {
Label("Start Build", systemImage: "hammer.fill")
}
.buttonStyle(.borderedProminent)
}
}
}

View File

@ -0,0 +1,331 @@
// CxLLM Studio iOS Application
// Views/Agent/AgentView.swift Agent workspace for iOS
import SwiftUI
import CxCode
struct AgentView: View {
@Environment(AgentService.self) private var agent
@State private var taskInput = ""
@State private var maxSteps = 10
@State private var output = ""
@State private var selectedTool: AgentToolInfo?
@State private var toolArgs = "{}"
@State private var toolResult = ""
@State private var showToolBrowser = false
private let agentGreen = Color(red: 0.46, green: 0.72, blue: 0.0)
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 20) {
statusSection
autopilotSection
quickTasksSection
if !output.isEmpty { outputSection }
toolExecutionSection
if !agent.history.isEmpty { historySection }
}
.padding()
}
.navigationTitle("Agent")
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Button { showToolBrowser = true } label: {
Label("Tools", systemImage: "wrench.and.screwdriver")
}
}
ToolbarItem(placement: .topBarTrailing) {
statusPill
}
}
.sheet(isPresented: $showToolBrowser) {
toolBrowserSheet
}
.task { await agent.initialize() }
}
// MARK: - Status
private var statusSection: some View {
HStack(spacing: 12) {
statCard("Tools", "\(agent.tools.count)", "wrench", agentGreen)
statCard("Runs", "\(agent.totalExecutions)", "play.circle", .blue)
statCard("Calls", "\(agent.totalToolCalls)", "arrow.right.circle", .purple)
}
}
private func statCard(_ label: String, _ value: String, _ icon: String, _ color: Color) -> some View {
VStack(spacing: 4) {
Image(systemName: icon).font(.system(size: 18)).foregroundStyle(color)
Text(value).font(.system(size: 20, weight: .bold, design: .rounded))
Text(label).font(.caption2).foregroundStyle(.secondary)
}
.frame(maxWidth: .infinity)
.padding(.vertical, 12)
.background(color.opacity(0.06))
.clipShape(RoundedRectangle(cornerRadius: 12))
}
// MARK: - Autopilot
private var autopilotSection: some View {
VStack(alignment: .leading, spacing: 8) {
Label("Autopilot", systemImage: "bolt.fill")
.font(.headline).foregroundStyle(agentGreen)
TextField("Describe a task...", text: $taskInput, axis: .vertical)
.lineLimit(2...5)
.textFieldStyle(.roundedBorder)
HStack {
HStack(spacing: 4) {
Text("Max steps:").font(.caption).foregroundStyle(.secondary)
TextField("", value: $maxSteps, format: .number)
.textFieldStyle(.roundedBorder)
.frame(width: 50)
.font(.caption)
}
Spacer()
Button {
runAutopilot()
} label: {
HStack(spacing: 4) {
if agent.isRunning {
ProgressView().controlSize(.small)
} else {
Image(systemName: "play.fill")
}
Text(agent.isRunning ? "Running..." : "Execute")
}
.font(.subheadline.weight(.semibold))
}
.buttonStyle(.borderedProminent).tint(agentGreen)
.disabled(taskInput.isEmpty || agent.isRunning || !agent.isHealthy)
}
}
}
// MARK: - Quick Tasks
private var quickTasksSection: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
quickTaskChip("Analyze project", "magnifyingglass")
quickTaskChip("Fix bugs", "ant")
quickTaskChip("Write tests", "checkmark.circle")
quickTaskChip("Refactor", "arrow.triangle.2.circlepath")
quickTaskChip("Document", "doc.text")
}
}
}
private func quickTaskChip(_ text: String, _ icon: String) -> some View {
Button { taskInput = text } label: {
Label(text, systemImage: icon).font(.caption)
.padding(.horizontal, 10).padding(.vertical, 6)
.background(Color.primary.opacity(0.05))
.clipShape(Capsule())
}.buttonStyle(.plain)
}
// MARK: - Output
private var outputSection: some View {
VStack(alignment: .leading, spacing: 6) {
HStack {
Label("Output", systemImage: "text.alignleft")
.font(.headline)
Spacer()
Button { UIPasteboard.general.string = output } label: {
Image(systemName: "doc.on.doc").font(.caption)
}
}
Text(output)
.font(.system(.caption, design: .monospaced))
.textSelection(.enabled)
.padding(10)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.primary.opacity(0.03))
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
// MARK: - Tool Execution
private var toolExecutionSection: some View {
VStack(alignment: .leading, spacing: 8) {
Label("Direct Tool Call", systemImage: "terminal")
.font(.headline)
if let tool = selectedTool {
HStack {
Label(tool.name, systemImage: "wrench").font(.subheadline.weight(.medium))
Spacer()
Button("Change") { showToolBrowser = true }
.font(.caption).buttonStyle(.bordered).controlSize(.small)
}
if let desc = tool.description {
Text(desc).font(.caption).foregroundStyle(.secondary)
}
} else {
Button { showToolBrowser = true } label: {
Label("Select a tool", systemImage: "plus.circle")
.frame(maxWidth: .infinity)
.padding(.vertical, 8)
}
.buttonStyle(.bordered)
}
Text("Arguments (JSON)").font(.caption).foregroundStyle(.secondary)
TextEditor(text: $toolArgs)
.font(.system(.caption, design: .monospaced))
.scrollContentBackground(.hidden)
.frame(minHeight: 60, maxHeight: 120)
.padding(8)
.background(Color.primary.opacity(0.03))
.clipShape(RoundedRectangle(cornerRadius: 8))
Button { execTool() } label: {
HStack(spacing: 4) {
if agent.isExecutingTool {
ProgressView().controlSize(.small)
} else {
Image(systemName: "play.fill")
}
Text("Execute")
}
.font(.subheadline.weight(.medium))
}
.buttonStyle(.borderedProminent).controlSize(.small)
.disabled(selectedTool == nil || agent.isExecutingTool)
if !toolResult.isEmpty {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text("Result").font(.caption.weight(.semibold)).foregroundStyle(.secondary)
Spacer()
Button { UIPasteboard.general.string = toolResult } label: {
Image(systemName: "doc.on.doc").font(.caption2)
}
}
Text(toolResult)
.font(.system(.caption, design: .monospaced))
.textSelection(.enabled)
.padding(8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.primary.opacity(0.03))
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
}
}
// MARK: - History
private var historySection: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Label("History", systemImage: "clock").font(.headline)
Spacer()
Button("Clear") { agent.clearHistory() }
.font(.caption).buttonStyle(.bordered).controlSize(.mini)
}
ForEach(agent.history) { h in
HStack(spacing: 10) {
Image(systemName: h.success ? "checkmark.circle.fill" : "xmark.circle.fill")
.foregroundStyle(h.success ? .green : .red)
.font(.body)
VStack(alignment: .leading, spacing: 2) {
Text(h.task).font(.subheadline.weight(.medium)).lineLimit(1)
Text("\(h.steps.count) steps · \(String(format: "%.1fs", h.duration))")
.font(.caption2).foregroundStyle(.secondary)
}
Spacer()
Text(h.time, format: .dateTime.hour().minute())
.font(.caption2.monospacedDigit()).foregroundStyle(.tertiary)
}
.padding(10)
.background(Color.primary.opacity(0.02))
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
}
// MARK: - Tool Browser Sheet
private var toolBrowserSheet: some View {
NavigationStack {
List(agent.tools, id: \.name) { tool in
Button {
selectedTool = tool
showToolBrowser = false
} label: {
VStack(alignment: .leading, spacing: 2) {
Text(tool.name).font(.subheadline.weight(.medium))
if let desc = tool.description {
Text(desc).font(.caption).foregroundStyle(.secondary).lineLimit(2)
}
}
}
}
.navigationTitle("Agent Tools")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Done") { showToolBrowser = false }
}
}
}
}
// MARK: - Components
private var statusPill: some View {
HStack(spacing: 4) {
Circle().fill(agent.isHealthy ? Color.green : Color.red.opacity(0.5)).frame(width: 6, height: 6)
Text(agent.isHealthy ? "Ready" : "Offline").font(.caption2.weight(.semibold))
}
.padding(.horizontal, 8).padding(.vertical, 4)
.background(agent.isHealthy ? Color.green.opacity(0.08) : Color.red.opacity(0.08))
.clipShape(Capsule())
}
// MARK: - Actions
private func runAutopilot() {
guard !taskInput.isEmpty else { return }
output = ""
Task {
do {
let execution = try await agent.runAutopilot(task: taskInput, maxSteps: maxSteps)
output = execution.output
} catch {
output = "Error: \(error.localizedDescription)"
}
}
}
private func execTool() {
guard let tool = selectedTool else { return }
toolResult = ""
Task {
do {
let args = parseJSON(toolArgs)
toolResult = try await agent.callTool(name: tool.name, arguments: args)
} catch {
toolResult = "Error: \(error.localizedDescription)"
}
}
}
private func parseJSON(_ s: String) -> [String: Any] {
guard let d = s.data(using: .utf8),
let o = try? JSONSerialization.jsonObject(with: d) as? [String: Any]
else { return [:] }
return o
}
}

View File

@ -0,0 +1,175 @@
// CxLLM Studio iOS Application
// Views/Chat/ChatView.swift iOS chat interface
import SwiftUI
import CxCode
import CxAWS
struct ChatView: View {
@Environment(AppState.self) private var appState
@Environment(GatewayService.self) private var gateway
@State private var inputText = ""
@State private var isStreaming = false
@State private var streamingText = ""
var body: some View {
VStack(spacing: 0) {
if let conv = appState.activeConversation {
ScrollViewReader { proxy in
ScrollView {
LazyVStack(alignment: .leading, spacing: 12) {
ForEach(conv.messages) { message in
MessageRow(message: message)
.id(message.id)
}
if isStreaming {
MessageRow(message: Message(role: .assistant, content: streamingText, isStreaming: true))
.id("streaming")
}
}
.padding()
}
.onChange(of: conv.messages.count) {
if let lastId = conv.messages.last?.id {
proxy.scrollTo(lastId, anchor: .bottom)
}
}
}
Divider()
// Input
HStack(spacing: 12) {
TextField("Message...", text: $inputText, axis: .vertical)
.textFieldStyle(.roundedBorder)
.lineLimit(1...5)
Button {
Task { await sendMessage() }
} label: {
Image(systemName: "arrow.up.circle.fill")
.font(.title2)
}
.disabled(inputText.isEmpty || isStreaming)
}
.padding()
} else {
// Welcome
VStack(spacing: 20) {
Spacer()
Image(systemName: "sparkles")
.font(.system(size: 48))
.foregroundStyle(.purple)
Text("CxLLM Studio")
.font(.title.bold())
Text("Tap + to start a conversation")
.foregroundStyle(.secondary)
Button("New Chat") {
appState.createConversation()
}
.buttonStyle(.borderedProminent)
Spacer()
}
}
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Menu {
ForEach(CxModelSlot.allCases) { slot in
Button {
appState.selectedModel = slot
appState.createConversation()
} label: {
Label(slot.codename, systemImage: slot.icon)
}
}
} label: {
Image(systemName: "plus")
}
}
ToolbarItem(placement: .principal) {
HStack(spacing: 6) {
Circle()
.fill(gateway.isConnected ? .green : .red)
.frame(width: 8, height: 8)
Text(appState.selectedModel.codename)
.font(.caption)
}
}
}
}
private func sendMessage() async {
let text = inputText.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty else { return }
inputText = ""
if appState.activeConversationId == nil {
appState.createConversation()
}
guard var conv = appState.activeConversation else { return }
conv.messages.append(.user(text))
appState.activeConversation = conv
if conv.sessionId == nil {
do {
let sessionId = try await gateway.createSession(model: appState.selectedModel.rawValue)
conv.sessionId = sessionId
appState.activeConversation = conv
} catch { return }
}
guard let sessionId = conv.sessionId else { return }
isStreaming = true
streamingText = ""
do {
let response = try await gateway.sendMessage(sessionId: sessionId, message: text, model: appState.selectedModel.rawValue)
conv.messages.append(.assistant(response.reply, model: appState.selectedModel.codename))
appState.activeConversation = conv
} catch {
conv.messages.append(.assistant("Error: \(error.localizedDescription)"))
appState.activeConversation = conv
}
isStreaming = false
}
}
struct MessageRow: View {
let message: Message
var body: some View {
HStack(alignment: .top, spacing: 10) {
Circle()
.fill(message.role == .user ? .blue : .purple)
.frame(width: 28, height: 28)
.overlay {
Image(systemName: message.role == .user ? "person.fill" : "sparkles")
.font(.caption2)
.foregroundStyle(.white)
}
VStack(alignment: .leading, spacing: 4) {
if message.isStreaming {
HStack {
Text(message.content)
ProgressView()
}
} else {
Text(LocalizedStringKey(message.content))
.textSelection(.enabled)
}
if let model = message.model {
Text(model)
.font(.caption2)
.foregroundStyle(.secondary)
}
}
Spacer()
}
}
}

View File

@ -0,0 +1,189 @@
// CxLLM Studio iOS GitView.swift
// Gitea repository dashboard adapted for iOS
import SwiftUI
import CxGit
struct GitView: View {
@Environment(GitService.self) private var git
enum Section: String, CaseIterable {
case repos = "Repos"
case pulls = "PRs"
case issues = "Issues"
}
@State private var section: Section = .repos
@State private var selectedRepo: GitRepository?
var body: some View {
VStack(spacing: 0) {
// Connection bar
HStack(spacing: 8) {
Circle().fill(git.isConnected ? .green : .red).frame(width: 8, height: 8)
Text(git.isConnected ? "Connected" : "Disconnected").font(.caption)
Spacer()
if let user = git.currentUser {
Label(user.login, systemImage: "person.circle").font(.caption).foregroundStyle(.secondary)
}
Button { Task { await git.refreshAll() } } label: {
Image(systemName: "arrow.clockwise").font(.caption)
}
}
.padding(.horizontal).padding(.vertical, 8)
.background(.ultraThinMaterial)
// Section picker
Picker("Section", selection: $section) {
ForEach(Section.allCases, id: \.self) { s in
Text(s.rawValue).tag(s)
}
}
.pickerStyle(.segmented)
.padding(.horizontal).padding(.vertical, 8)
// Content
switch section {
case .repos: reposList
case .pulls: pullsList
case .issues: issuesList
}
}
.task {
await git.refreshAll()
}
}
// MARK: - Repos
private var reposList: some View {
Group {
if git.repositories.isEmpty {
ContentUnavailableView("No Repositories", systemImage: "folder",
description: Text("Check your Gitea connection"))
} else {
List(git.repositories) { repo in
NavigationLink {
repoDetail(repo)
} label: {
HStack(spacing: 8) {
Image(systemName: repo.private ? "lock.fill" : "globe")
.foregroundStyle(repo.private ? .orange : .green)
.font(.caption)
VStack(alignment: .leading, spacing: 2) {
Text(repo.name).font(.subheadline.weight(.medium))
if let desc = repo.description, !desc.isEmpty {
Text(desc).font(.caption).foregroundStyle(.secondary).lineLimit(1)
}
}
Spacer()
HStack(spacing: 6) {
Label("\(repo.starsCount)", systemImage: "star").font(.caption2)
Label("\(repo.openIssuesCount)", systemImage: "exclamationmark.circle").font(.caption2)
}.foregroundStyle(.secondary)
}
}
}
}
}
}
private func repoDetail(_ repo: GitRepository) -> some View {
List {
SwiftUI.Section("Info") {
LabeledContent("Name", value: repo.name)
LabeledContent("Default Branch", value: repo.defaultBranch)
LabeledContent("Stars", value: "\(repo.starsCount)")
LabeledContent("Forks", value: "\(repo.forksCount)")
LabeledContent("Open Issues", value: "\(repo.openIssuesCount)")
if let desc = repo.description { LabeledContent("Description", value: desc) }
}
SwiftUI.Section {
Button("Load PRs") {
let parts = repo.fullName.components(separatedBy: "/")
if parts.count == 2 {
Task { await git.loadPullRequests(owner: parts[0], repo: parts[1]); section = .pulls }
}
}
Button("Load Issues") {
let parts = repo.fullName.components(separatedBy: "/")
if parts.count == 2 {
Task { await git.loadIssues(owner: parts[0], repo: parts[1]); section = .issues }
}
}
Button("Load Branches") {
let parts = repo.fullName.components(separatedBy: "/")
if parts.count == 2 {
Task { await git.loadBranches(owner: parts[0], repo: parts[1]) }
}
}
}
}
.navigationTitle(repo.name)
}
// MARK: - Pull Requests
private var pullsList: some View {
Group {
if git.pullRequests.isEmpty {
ContentUnavailableView("No Pull Requests", systemImage: "arrow.triangle.pull",
description: Text("Select a repo to load PRs"))
} else {
List(git.pullRequests) { pr in
HStack(spacing: 8) {
Image(systemName: pr.state == "open" ? "arrow.triangle.pull" : "checkmark.circle.fill")
.foregroundStyle(pr.state == "open" ? .green : .purple)
VStack(alignment: .leading, spacing: 2) {
Text("#\(pr.number) \(pr.title)").font(.subheadline.weight(.medium))
HStack(spacing: 6) {
if let user = pr.user { Text(user.login).font(.caption2) }
if let head = pr.head, let base = pr.base {
Text("\(head.ref)\(base.ref)").font(.caption2).foregroundStyle(.secondary)
}
}
}
Spacer()
Text(pr.state).font(.caption2)
.padding(.horizontal, 6).padding(.vertical, 2)
.background(pr.state == "open" ? Color.green.opacity(0.1) : Color.purple.opacity(0.1))
.clipShape(Capsule())
}
}
}
}
}
// MARK: - Issues
private var issuesList: some View {
Group {
if git.issues.isEmpty {
ContentUnavailableView("No Issues", systemImage: "exclamationmark.circle",
description: Text("Select a repo to load issues"))
} else {
List(git.issues) { issue in
HStack(spacing: 8) {
Image(systemName: issue.state == "open" ? "circle" : "checkmark.circle.fill")
.foregroundStyle(issue.state == "open" ? .green : .secondary)
VStack(alignment: .leading, spacing: 2) {
Text("#\(issue.number) \(issue.title)").font(.subheadline.weight(.medium))
HStack(spacing: 4) {
if let user = issue.user { Text(user.login).font(.caption2).foregroundStyle(.secondary) }
if let labels = issue.labels {
ForEach(labels.prefix(3)) { label in
Text(label.name).font(.system(size: 9))
.padding(.horizontal, 4).padding(.vertical, 1)
.background(Color.secondary.opacity(0.1)).clipShape(Capsule())
}
}
}
}
Spacer()
}
}
}
}
}
}

View File

@ -0,0 +1,385 @@
// CxLLM Studio iOS Application
// Views/MCP/MCPView.swift MCP protocol inspector for iOS
import SwiftUI
import CxCode
struct MCPView: View {
@Environment(MCPService.self) private var mcp
enum Tab: String, CaseIterable {
case tools, resources, history
var label: String { rawValue.capitalized }
var icon: String {
switch self {
case .tools: return "wrench.and.screwdriver"
case .resources: return "folder"
case .history: return "clock.arrow.circlepath"
}
}
}
@State private var tab: Tab = .tools
@State private var selectedToolName: String?
@State private var toolArgs = "{}"
@State private var toolResult = ""
@State private var selectedResource: McpResource?
@State private var resourceContent = ""
@State private var toolSearch = ""
private let mcpTeal = Color(red: 0.0, green: 0.72, blue: 0.72)
var body: some View {
VStack(spacing: 0) {
connectionBar
Picker("Tab", selection: $tab) {
ForEach(Tab.allCases, id: \.self) { t in
Label(t.label, systemImage: t.icon).tag(t)
}
}
.pickerStyle(.segmented)
.padding(.horizontal)
.padding(.vertical, 8)
switch tab {
case .tools: toolsTab
case .resources: resourcesTab
case .history: historyTab
}
}
.navigationTitle("MCP")
.toolbar {
ToolbarItemGroup(placement: .topBarTrailing) {
Button { Task { let _ = await mcp.ping() } } label: {
Image(systemName: "antenna.radiowaves.left.and.right")
}
Button { Task { await mcp.reload() } } label: {
Image(systemName: "arrow.clockwise")
}
}
}
.task { await mcp.initialize() }
}
// MARK: - Connection Bar
private var connectionBar: some View {
HStack(spacing: 8) {
Circle()
.fill(mcp.isInitialized ? (mcp.pingOk ? Color.green : .yellow) : Color.red.opacity(0.5))
.frame(width: 8, height: 8)
Text(mcp.statusMessage)
.font(.caption).foregroundStyle(.secondary).lineLimit(1)
Spacer()
HStack(spacing: 12) {
statLabel("Tools", mcp.tools.count)
statLabel("Resources", mcp.resources.count)
}
}
.padding(.horizontal)
.padding(.vertical, 6)
.background(Color.primary.opacity(0.03))
}
private func statLabel(_ label: String, _ count: Int) -> some View {
HStack(spacing: 2) {
Text("\(count)").font(.caption.weight(.bold).monospacedDigit())
Text(label).font(.caption2).foregroundStyle(.secondary)
}
}
// MARK: - Tools Tab
private var toolsTab: some View {
VStack(spacing: 0) {
HStack(spacing: 6) {
Image(systemName: "magnifyingglass").foregroundStyle(.tertiary)
TextField("Search tools...", text: $toolSearch)
.textFieldStyle(.plain)
if !toolSearch.isEmpty {
Button { toolSearch = "" } label: {
Image(systemName: "xmark.circle.fill").foregroundStyle(.tertiary)
}
}
}
.font(.subheadline)
.padding(8)
.background(Color.primary.opacity(0.04))
.clipShape(RoundedRectangle(cornerRadius: 8))
.padding(.horizontal)
.padding(.bottom, 4)
List(filteredTools, id: \.name, selection: $selectedToolName) { tool in
NavigationLink(value: tool.name) {
VStack(alignment: .leading, spacing: 2) {
Text(tool.name).font(.subheadline.weight(.medium))
if let desc = tool.description {
Text(desc).font(.caption).foregroundStyle(.secondary).lineLimit(1)
}
}
}
}
.listStyle(.plain)
.navigationDestination(for: String.self) { name in
if let tool = mcp.tool(named: name) {
toolDetailView(tool)
}
}
}
}
private var filteredTools: [McpToolDef] {
mcp.filteredTools(query: toolSearch)
}
private func toolDetailView(_ tool: McpToolDef) -> some View {
ScrollView {
VStack(alignment: .leading, spacing: 12) {
Text(tool.name)
.font(.title3.weight(.bold).monospaced())
if let desc = tool.description {
Text(desc).font(.subheadline).foregroundStyle(.secondary)
}
if let schema = tool.inputSchema?.dictValue {
Divider()
Text("Input Schema").font(.caption.weight(.semibold)).foregroundStyle(.tertiary)
schemaView(schema)
}
Divider()
Text("Arguments (JSON)").font(.caption.weight(.semibold)).foregroundStyle(.tertiary)
TextEditor(text: $toolArgs)
.font(.system(.caption, design: .monospaced))
.scrollContentBackground(.hidden)
.frame(minHeight: 80)
.padding(8)
.background(Color.primary.opacity(0.03))
.clipShape(RoundedRectangle(cornerRadius: 8))
Button { callTool(tool.name) } label: {
HStack(spacing: 4) {
if mcp.isExecutingTool {
ProgressView().controlSize(.small)
} else {
Image(systemName: "play.fill")
}
Text("Execute")
}
.font(.subheadline.weight(.medium))
}
.buttonStyle(.borderedProminent).tint(mcpTeal)
.disabled(mcp.isExecutingTool)
if !toolResult.isEmpty {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text("Result").font(.caption.weight(.semibold)).foregroundStyle(.tertiary)
Spacer()
Button { UIPasteboard.general.string = toolResult } label: {
Image(systemName: "doc.on.doc").font(.caption2)
}
}
Text(toolResult)
.font(.system(.caption, design: .monospaced))
.textSelection(.enabled)
.padding(8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.primary.opacity(0.03))
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
}
.padding()
}
.navigationTitle(tool.name)
.navigationBarTitleDisplayMode(.inline)
}
private func schemaView(_ dict: [String: Any]) -> some View {
VStack(alignment: .leading, spacing: 4) {
if let props = dict["properties"] as? [String: Any] {
let required = dict["required"] as? [String] ?? []
ForEach(Array(props.keys.sorted()), id: \.self) { key in
let info = props[key] as? [String: Any] ?? [:]
let typ = info["type"] as? String ?? "any"
let desc = info["description"] as? String
let isReq = required.contains(key)
VStack(alignment: .leading, spacing: 1) {
HStack(spacing: 4) {
Text(key).font(.caption.weight(.medium).monospaced())
if isReq {
Text("*").font(.caption.weight(.bold)).foregroundStyle(.red)
}
Spacer()
Text(typ).font(.caption2.monospaced()).foregroundStyle(.tertiary)
}
if let d = desc {
Text(d).font(.caption2).foregroundStyle(.quaternary)
}
}
}
}
}
.padding(8)
.background(Color.primary.opacity(0.03))
.clipShape(RoundedRectangle(cornerRadius: 8))
}
// MARK: - Resources Tab
private var resourcesTab: some View {
List(mcp.resources, id: \.uri) { resource in
NavigationLink {
resourceDetailView(resource)
} label: {
VStack(alignment: .leading, spacing: 2) {
Text(resource.name).font(.subheadline.weight(.medium))
Text(resource.uri).font(.caption.monospaced()).foregroundStyle(.secondary).lineLimit(1)
if let mime = resource.mimeType {
Text(mime).font(.caption2).foregroundStyle(.tertiary)
.padding(.horizontal, 6).padding(.vertical, 2)
.background(Color.primary.opacity(0.04))
.clipShape(Capsule())
}
}
}
}
.listStyle(.plain)
.overlay {
if mcp.resources.isEmpty {
ContentUnavailableView("No Resources", systemImage: "folder", description: Text("Connect to the MCP server to discover resources."))
}
}
}
private func resourceDetailView(_ resource: McpResource) -> some View {
ScrollView {
VStack(alignment: .leading, spacing: 12) {
Text(resource.name).font(.title3.weight(.bold))
Text(resource.uri).font(.caption.monospaced()).foregroundStyle(.secondary).textSelection(.enabled)
if let mime = resource.mimeType {
Text(mime).font(.caption2).foregroundStyle(.tertiary)
.padding(.horizontal, 8).padding(.vertical, 3)
.background(Color.primary.opacity(0.04))
.clipShape(Capsule())
}
if let desc = resource.description {
Text(desc).font(.subheadline).foregroundStyle(.secondary)
}
Button { readResource(resource) } label: {
HStack(spacing: 4) {
if mcp.isReadingResource {
ProgressView().controlSize(.small)
} else {
Image(systemName: "doc.text.magnifyingglass")
}
Text("Read Content")
}
.font(.subheadline.weight(.medium))
}
.buttonStyle(.borderedProminent).tint(.blue)
.disabled(mcp.isReadingResource)
if !resourceContent.isEmpty {
VStack(alignment: .leading, spacing: 4) {
HStack {
Text("Content").font(.caption.weight(.semibold)).foregroundStyle(.tertiary)
Spacer()
Button { UIPasteboard.general.string = resourceContent } label: {
Image(systemName: "doc.on.doc").font(.caption2)
}
}
Text(resourceContent)
.font(.system(.caption, design: .monospaced))
.textSelection(.enabled)
.padding(8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color.primary.opacity(0.03))
.clipShape(RoundedRectangle(cornerRadius: 8))
}
}
}
.padding()
}
.navigationTitle(resource.name)
.navigationBarTitleDisplayMode(.inline)
}
// MARK: - History Tab
private var historyTab: some View {
Group {
if mcp.history.isEmpty {
ContentUnavailableView("No History",
systemImage: "clock.arrow.circlepath",
description: Text("Execute tools or read resources to see history."))
} else {
List {
Section {
ForEach(mcp.history) { entry in
HStack(spacing: 10) {
Image(systemName: entry.type == .tool ? "wrench" : "doc")
.foregroundStyle(entry.type == .tool ? .green : .blue)
.frame(width: 24)
VStack(alignment: .leading, spacing: 2) {
Text(entry.name).font(.subheadline.weight(.medium))
Text(String(entry.result.prefix(80)))
.font(.caption).foregroundStyle(.secondary).lineLimit(1)
}
Spacer()
VStack(alignment: .trailing, spacing: 2) {
Text(entry.time, format: .dateTime.hour().minute().second())
.font(.caption2.monospacedDigit()).foregroundStyle(.tertiary)
Text(entry.success ? "OK" : "ERR")
.font(.caption2.weight(.bold))
.foregroundStyle(entry.success ? .green : .red)
}
}
}
} header: {
HStack {
Text("\(mcp.history.count) entries")
Spacer()
Button("Clear") { mcp.clearHistory() }
.font(.caption)
}
}
}
.listStyle(.insetGrouped)
}
}
}
// MARK: - Actions
private func callTool(_ name: String) {
toolResult = ""
Task {
let result = await mcp.callTool(name: name, arguments: parseJSON(toolArgs))
toolResult = result.content
}
}
private func readResource(_ res: McpResource) {
resourceContent = ""
Task {
resourceContent = await mcp.readResource(uri: res.uri, name: res.name)
}
}
private func parseJSON(_ s: String) -> [String: Any] {
guard let d = s.data(using: .utf8),
let o = try? JSONSerialization.jsonObject(with: d) as? [String: Any]
else { return [:] }
return o
}
}

View File

@ -0,0 +1,179 @@
// CxLLM Studio iOS Application
// Views/Models/ModelsView.swift CxModel browser for iOS
import SwiftUI
import CxCode
import CxAWS
struct ModelsView: View {
@Environment(AppState.self) private var appState
@Environment(CxModelController.self) private var modelController
var body: some View {
List {
ForEach(CxModelSlot.allCases) { slot in
NavigationLink {
ModelDetailView(slot: slot)
} label: {
ModelRow(slot: slot)
}
}
}
}
}
struct ModelRow: View {
let slot: CxModelSlot
var body: some View {
HStack(spacing: 12) {
Image(systemName: slot.icon)
.font(.title2)
.foregroundStyle(tierColor(slot.tier))
.frame(width: 36)
VStack(alignment: .leading, spacing: 2) {
Text(slot.codename)
.font(.headline)
Text(slot.rawValue)
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
Text(slot.tier)
.font(.caption2)
.padding(.horizontal, 8)
.padding(.vertical, 3)
.background(tierColor(slot.tier).opacity(0.15))
.clipShape(Capsule())
}
}
private func tierColor(_ tier: String) -> Color {
switch tier {
case "fast": return .green
case "balanced": return .blue
case "premium": return .purple
case "safety": return .orange
case "ultra": return .red
default: return .gray
}
}
}
struct ModelDetailView: View {
let slot: CxModelSlot
@Environment(AppState.self) private var appState
@State private var testPrompt = "Hello, introduce yourself in one sentence."
@State private var testResult = ""
@State private var isTesting = false
@Environment(GatewayService.self) private var gateway
var body: some View {
List {
Section("Model Info") {
LabeledContent("Codename", value: slot.codename)
LabeledContent("Slot", value: slot.rawValue)
LabeledContent("Provider", value: slot.provider)
LabeledContent("Model", value: slot.defaultModel)
LabeledContent("Tier", value: slot.tier)
}
Section("Capabilities") {
FlowLayout(spacing: 6) {
ForEach(slot.capabilities, id: \.self) { cap in
Text(cap)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(.blue.opacity(0.1))
.clipShape(Capsule())
}
}
}
Section("Recommendation") {
Text(slot.recommendation)
.font(.callout)
.foregroundStyle(.secondary)
}
Section("Test Inference") {
TextField("Prompt", text: $testPrompt, axis: .vertical)
.lineLimit(2...4)
Button {
Task { await testInference() }
} label: {
Label(isTesting ? "Testing..." : "Run Test", systemImage: "play.fill")
}
.disabled(isTesting)
if !testResult.isEmpty {
Text(testResult)
.font(.callout)
.textSelection(.enabled)
}
}
}
.navigationTitle(slot.codename)
}
private func testInference() async {
isTesting = true
testResult = ""
do {
let sessionId = try await gateway.createSession(model: slot.rawValue)
let response = try await gateway.sendMessage(sessionId: sessionId, message: testPrompt, model: slot.rawValue)
testResult = response.reply
} catch {
testResult = "Error: \(error.localizedDescription)"
}
isTesting = false
}
}
struct FlowLayout: Layout {
var spacing: CGFloat = 6
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let result = arrangeSubviews(proposal: proposal, subviews: subviews)
return result.size
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let result = arrangeSubviews(proposal: proposal, subviews: subviews)
for (index, position) in result.positions.enumerated() {
subviews[index].place(at: CGPoint(x: bounds.minX + position.x, y: bounds.minY + position.y),
proposal: ProposedViewSize(result.sizes[index]))
}
}
private func arrangeSubviews(proposal: ProposedViewSize, subviews: Subviews) -> (positions: [CGPoint], sizes: [CGSize], size: CGSize) {
let maxWidth = proposal.width ?? .infinity
var positions: [CGPoint] = []
var sizes: [CGSize] = []
var x: CGFloat = 0
var y: CGFloat = 0
var rowHeight: CGFloat = 0
var maxX: CGFloat = 0
for subview in subviews {
let size = subview.sizeThatFits(.unspecified)
if x + size.width > maxWidth && x > 0 {
x = 0
y += rowHeight + spacing
rowHeight = 0
}
positions.append(CGPoint(x: x, y: y))
sizes.append(size)
rowHeight = max(rowHeight, size.height)
x += size.width + spacing
maxX = max(maxX, x)
}
return (positions, sizes, CGSize(width: maxX, height: y + rowHeight))
}
}

View File

@ -0,0 +1,90 @@
// CxLLM Studio iOS Application
// Views/Settings/SettingsView.swift iOS app configuration
import SwiftUI
import CxCode
import CxAWS
struct SettingsView: View {
@Environment(AppState.self) private var appState
@Environment(GatewayService.self) private var gateway
@Environment(AWSService.self) private var aws
@State private var baseURL = UserDefaults.standard.string(forKey: "cxllm_url") ?? "https://cxllm-studio.com"
@State private var apiKey = UserDefaults.standard.string(forKey: "cxllm_api_key") ?? ""
@State private var awsKey = UserDefaults.standard.string(forKey: "aws_access_key") ?? ""
@State private var awsSecret = UserDefaults.standard.string(forKey: "aws_secret_key") ?? ""
@State private var awsRegion = UserDefaults.standard.string(forKey: "aws_region") ?? "us-east-2"
@State private var healthResult: String?
var body: some View {
Form {
Section("Gateway") {
TextField("Base URL", text: $baseURL)
.keyboardType(.URL)
.autocapitalization(.none)
SecureField("API Key", text: $apiKey)
}
Section("AWS") {
TextField("Access Key ID", text: $awsKey)
.autocapitalization(.none)
SecureField("Secret Access Key", text: $awsSecret)
TextField("Region", text: $awsRegion)
.autocapitalization(.none)
}
Section("Default Model") {
Picker("Model", selection: Bindable(appState).selectedModel) {
ForEach(CxModelSlot.allCases) { slot in
Label(slot.codename, systemImage: slot.icon)
.tag(slot)
}
}
}
Section("Status") {
LabeledContent("Gateway") {
HStack {
Circle()
.fill(gateway.isConnected ? .green : .red)
.frame(width: 8, height: 8)
Text(gateway.isConnected ? "Connected" : "Offline")
}
}
LabeledContent("AWS") {
HStack {
Circle()
.fill(aws.isConnected ? .green : .red)
.frame(width: 8, height: 8)
Text(aws.isConnected ? "Connected" : "Offline")
}
}
Button("Check Health") {
Task {
await gateway.checkHealth()
await aws.checkHealth()
healthResult = "Checked"
}
}
}
Section("About") {
LabeledContent("App", value: "CxLLM Studio 1.0")
LabeledContent("Platform", value: "iOS")
LabeledContent("Models", value: "7 cx-model slots")
}
Section {
Button("Save Settings") {
UserDefaults.standard.set(baseURL, forKey: "cxllm_url")
UserDefaults.standard.set(apiKey, forKey: "cxllm_api_key")
UserDefaults.standard.set(awsKey, forKey: "aws_access_key")
UserDefaults.standard.set(awsSecret, forKey: "aws_secret_key")
UserDefaults.standard.set(awsRegion, forKey: "aws_region")
}
.buttonStyle(.borderedProminent)
}
}
}
}

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 CxAI-LLM
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

43
Makefile Normal file
View File

@ -0,0 +1,43 @@
# Makefile — common entry points for CxLLM-IOS
SHELL := /bin/bash
.DEFAULT_GOAL := help
PROJECT := CxLLM-IOS
SCHEME ?= $(PROJECT)
CONFIG ?= Debug
DERIVED ?= build
.PHONY: help build test lint clean fmt info
help: ## show this help
@grep -E '^[a-zA-Z_-]+:.*## ' $(MAKEFILE_LIST) | awk -F':.*## ' '{printf " %-12s %s\n", $$1, $$2}'
info: ## show project info
@echo "project : $(PROJECT)"
@echo "scheme : $(SCHEME)"
@echo "config : $(CONFIG)"
@echo "derived : $(DERIVED)"
build: ## xcodebuild build (skip if no .xcodeproj)
@if ls *.xcodeproj 1>/dev/null 2>&1; then \
xcodebuild -project "$(PROJECT).xcodeproj" -scheme "$(SCHEME)" -configuration "$(CONFIG)" -derivedDataPath "$(DERIVED)" build | xcbeautify || true ; \
elif [ -f Package.swift ]; then \
swift build -c $(shell echo "$(CONFIG)" | tr '[:upper:]' '[:lower:]') ; \
else echo "no buildable target" ; fi
test: ## xcodebuild test
@if ls *.xcodeproj 1>/dev/null 2>&1; then \
xcodebuild -project "$(PROJECT).xcodeproj" -scheme "$(SCHEME)" -configuration "$(CONFIG)" -derivedDataPath "$(DERIVED)" test | xcbeautify || true ; \
elif [ -f Package.swift ]; then \
swift test ; \
else echo "no testable target" ; fi
lint: ## swiftlint + swiftformat (no-op if missing)
@command -v swiftlint >/dev/null && swiftlint --quiet || echo "swiftlint not installed"
@command -v swiftformat >/dev/null && swiftformat --lint . || echo "swiftformat not installed"
fmt: ## swiftformat in-place
@command -v swiftformat >/dev/null && swiftformat . || echo "swiftformat not installed"
clean: ## remove build artifacts
rm -rf "$(DERIVED)" .build DerivedData

23
Package.swift Normal file
View File

@ -0,0 +1,23 @@
// swift-tools-version: 5.9
// CxLLM Studio iOS Application
import PackageDescription
let package = Package(
name: "CxLLMStudio-iOS",
platforms: [.iOS(.v17)],
dependencies: [
.package(url: "https://git.cxllm-studio.com/CxAI-LLM/CxLLM-SDK.git", branch: "main"),
],
targets: [
.executableTarget(
name: "CxLLMStudio",
dependencies: [
.product(name: "CxCode", package: "CxLLM-SDK"),
.product(name: "CxAWS", package: "CxLLM-SDK"),
.product(name: "CxGit", package: "CxLLM-SDK"),
],
path: "CxLLMStudio"
),
]
)

53
README.md Normal file
View File

@ -0,0 +1,53 @@
# CxLLM-IOS
> CxLLM iOS application
SwiftUI/SwiftData iOS host application embedding CxLLM.framework with on-device inference, App Group sharing, and background tasks.
[![ci](https://git.cxllm-studio.com/CxAI-LLM/CxLLM-iOS/actions/workflows/ci.yml/badge.svg)](https://git.cxllm-studio.com/CxAI-LLM/CxLLM-iOS/actions)
[![license](https://img.shields.io/badge/license-MIT-7C3AED)](LICENSE)
[![category](https://img.shields.io/badge/category-app-ios-1F6FEB)](#)
## Overview
`CxLLM-IOS` is part of the **CxLLM** product family — a layered runtime that
spans Apple kernel extensions, user-space frameworks, host applications, and
spatial / web surfaces. This module focuses on **app-ios** concerns and is
intentionally narrow so that the CxLLM monorepo can compose targets without
pulling in unrelated dependencies.
## Repository layout
- `CxLLM-IOS/` — primary source folder (matches the Xcode group / SwiftPM root).
- `CxLLM-IOS.xcodeproj` — Xcode project (where applicable).
- `Makefile` — common entry points (`build`, `test`, `lint`, `clean`).
- `.github/workflows/ci.yml` — CI pipeline (Xcode build + lint).
- `docs/` — architecture notes and ADRs.
## Quick start
```bash
# Build (defaults to Debug for the host platform):
make build
# Run the full test suite (unit + UI where applicable):
make test
# Static analysis + format check:
make lint
```
## Versioning
This module follows [Semantic Versioning 2.0](https://semver.org/) and is
released in lock-step with the umbrella **CxLLM** product line. See
[`CHANGELOG.md`](CHANGELOG.md) for the user-facing change log.
## Security
Please report security issues per [`SECURITY.md`](SECURITY.md). Do **not**
open public issues for vulnerabilities.
## License
Released under the [MIT License](LICENSE) © 2026 CxAI-LLM.

19
SECURITY.md Normal file
View File

@ -0,0 +1,19 @@
# Security policy for CxLLM-IOS
## Reporting a vulnerability
Please email **security@cxllm-studio.com** with:
- A description of the vulnerability and its impact.
- Steps to reproduce, ideally with a minimal proof-of-concept.
- The affected version(s) / commit SHAs.
We aim to acknowledge within **2 business days** and to publish a fix or
mitigation within **30 days** for high-severity issues.
Do **not** open a public Gitea / GitHub issue for vulnerabilities.
## Supported versions
Only the `main` branch and the most recent tagged release receive security
updates.

24
docs/ARCHITECTURE.md Normal file
View File

@ -0,0 +1,24 @@
# Architecture — CxLLM-IOS
> CxLLM iOS application
## Goals
- Provide a focused, well-tested app-ios surface for the CxLLM family.
- Stay deployable on its own (no monorepo coupling).
- Keep the public ABI small and explicitly versioned.
## Boundaries
```text
+----------------------+
client --> | CxLLM-IOS |
+----------+-----------+
|
v
CxLLM runtime / kernel
```
## Decisions
- See `docs/adr/` for Architecture Decision Records.

View File

@ -0,0 +1,13 @@
# 1. Record architecture decisions
## Status
Accepted
## Context
We need a lightweight, append-only log of architectural choices.
## Decision
Use Markdown ADRs in `docs/adr/`, numbered sequentially.
## Consequences
Future maintainers can read the chain of decisions without spelunking PR history.