new features

This commit is contained in:
Mark Gregory
2026-11-09 08:20:57 +00:00
parent ecfa74a35f
commit 965ae923c2
27 changed files with 1474 additions and 168 deletions
+2
View File
@@ -4,3 +4,5 @@ DerivedData/
*.xcuserstate
xcuserdata/
*.bak-*
.swiftpm/
+277
View File
@@ -0,0 +1,277 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="BackendCodeEditorSettings">
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CDeclarationWithImplicitIntType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CommentTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=ConstevalIfIsAlwaysConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractClassWithoutSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAbstractVirtualFunctionCallInCtor/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAccessSpecifierWithNoDeclarations/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAssignedValueIsNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppAwaiterTypeIsNotClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBooleanIncrementExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatBadCode/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatLegacyCode/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatMixedArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooFewArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppBoostFormatTooManyArgs/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCVQualifierCanNotBeAppliedToReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassCanBeFinal/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassIsIncomplete/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeedsConstructorBecauseOfUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppClassNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCompileTimeConstantCanBeReplacedWithBooleanConstant/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConceptNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConditionalExpressionCanBeSimplified/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstValueFunctionReturnType/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppConstexprIfDiscardedBranch/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCoroutineCallResolveError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaConflictingMemorySpaceSpecifiers/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaForLoopCanBeReplacedWithIRange/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaFunctionIsNotCallableFromExecutionSpace/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaGotoStatementIsNotAllowedInTileGlobalFunction/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaGridConstantIsOnlyAllowedOnKernelParameter/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaGridConstantParameterMustBeConstNonReference/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaIRangeStepMustBePositive/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaMemorySpaceLocalVariableMustBeExternOrStatic/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaMemorySpaceSpecifierIsNotAllowedOnDataMember/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaMemorySpaceSpecifierIsNotAllowedOnParameter/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaMemorySpaceSpecifierIsNotAllowedOnTypeAlias/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaMemorySpaceVariableIsNotAllowedInHostFunction/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaReturnStatementInsideLoopIsNotAllowedInTileGlobalFunction/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaSharedVariableCannotHaveInitializer/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaTileKernelLaunchMustHaveBlockSizeOne/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppCudaVariableIsNotAccessibleFromExecutionSpace/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAArrayIndexOutOfBounds/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantConditions/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantFunctionResult/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAConstantParameter/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFADeletedPointer/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAEndlessLoop/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInfiniteRecursion/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAInvalidatedMemory/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesFunction/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALocalValueEscapesScope/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFALoopConditionNotUpdated/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAMemoryLeak/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANotInitializedField/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFANullDereference/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFATimeOver/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableCode/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreachableFunctionCall/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnreadVariable/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDFAUnusedValue/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesLocal/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationHidesUncapturedLocal/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclarationSpecifierWithoutDeclarators/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorDisambiguatedAsFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeclaratorUsedBeforeInitialization/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultCaseNotHandledInSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultInitializationWithNoUserConstructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultIsUsedAsIdentifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefaultedSpecialMemberFunctionIsImplicitlyDeleted/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDefinitionsOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeletingVoidPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTemplateWithoutTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDependentTypeWithoutTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedEntity/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedOverridenMethod/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDeprecatedRegisterStorageClassSpecifier/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDereferenceOperatorLimitExceeded/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDiscardedPostfixOperatorResult/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenSyntaxError/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUndocumentedParameter/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppDoxygenUnresolvedReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEmptyDeclaration/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersOrder/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceCVQualifiersPlacement/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceDoStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceForStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceFunctionDeclarationStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceIfStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceNestedNamespacesStyle/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingDestructorStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceOverridingFunctionStyle/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceTypeAliasCodeStyle/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnforceWhileStatementBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityAssignedButNoRead/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEntityUsedOnlyInUnevaluatedContext/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEnumeratorNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEqualOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppEvaluationFailure/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExplicitSpecializationInNonNamespaceScope/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppExpressionWithoutSideEffects/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalFunctionInFinalClass/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFinalNonOverridingVirtualFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForLoopCanBeReplacedWithWhile/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppForwardEnumDeclarationWithoutUnderlyingType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionDoesntReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionResultShouldBeUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppFunctionalStyleCast/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHeaderHasBeenAlreadyIncluded/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHiddenFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppHidingFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIdenticalOperandsInBinaryExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIfCanBeReplacedByConstexprIf/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppImplicitDefaultConstructorNotAvailable/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompatiblePointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIncompleteSwitchStatement/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInconsistentNaming/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInitializedValueIsAlwaysRewritten/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppIntegralToPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppInvalidLineContinuation/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppJoinDeclarationAndAssignment/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLambdaCaptureNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLocalVariableWithNonTrivialDtorIsNeverUsed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppLongFloat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeConst/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberFunctionMayBeStatic/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMemberInitializersOrder/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMismatchedClassTags/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingIncludeGuard/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMissingKeywordThrow/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppModulePartitionWithSeveralPartitionUnits/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtAddressOfClassRValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtAddressOfParenthesizedMemberFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtBindingRValueToLvalueReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtCopyElisionInCopyInitDeclarator/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtDoubleUserConversionInCopyInit/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtNotInitializedStaticConstLocalVar/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMsExtReinterpretCastFromNullptr/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterLiteral/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMultiCharacterWideLiteral/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMustBePublicVirtualToImplementInterface/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppMutableSpecifierOnReferenceMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNoDiscardExpression/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNodiscardFunctionWithoutReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExceptionSafeResourceAcquisition/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConversionOperator/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonExplicitConvertingConstructor/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineFunctionDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNonInlineVariableDefinitionInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppNotAllPathsReturnValue/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppObjectMemberMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOutParameterMustBeWritten/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppOverrideWithDifferentVisibility/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConst/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterMayBeConstPtrOrRef/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNamesMismatch/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppParameterValueIsReassigned/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPassValueParameterByConstReference/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerConversionDropsQualifiers/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPointerToIntegralConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPolymorphicClassWithNonVirtualPublicDestructor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyErroneousEmptyStatements/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUninitializedMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPossiblyUnintendedObjectSlicing/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderIsNotIncluded/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrecompiledHeaderNotFound/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfBadFormat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfExtraArg/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfMissedArg/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfMixedArgs/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrintfRiskyFormat/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppPrivateSpecialMemberFunctionIsNotImplemented/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRangeBasedForIncompatibleReference/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedefinitionOfDefaultArgumentInOverrideFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassAccessSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBaseClassInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantBooleanExpressionArgument/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantCastExpression/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantComplexityInComparison/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConditionalExpression/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantConstSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantControlFlowJump/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantDereferencingAndTakingAddress/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElaboratedTypeSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeyword/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantElseKeywordInsideCompoundStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyDeclaration/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantEmptyStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantExportKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantFwdClassOrEnumSpecifier/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantInlineSpecifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantLambdaParameterList/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantMemberInitializer/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantNamespaceDefinition/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantParentheses/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifier/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantQualifierADL/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnMemberAllocationFunction/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantStaticSpecifierOnThreadLocalLocalVariable/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateArguments/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTemplateKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantTypenameKeyword/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantVoidArgumentList/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRedundantZeroInitializerInAggregateInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReferenceToOverriddenVirtualFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReinterpretCastFromVoidPtr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppRemoveRedundantBraces/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceMemsetWithZeroInitialization/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReplaceTieWithStructuredBinding/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppReturnNoValueInNonVoidFunction/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSmartPointerVsMakeFunction/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSomeObjectMembersMightNotBeInitialized/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSpecialFunctionWithoutNoexceptSpecification/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticAssertFailure/@EntryIndexedValue" value="ERROR" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticDataMemberInUnnamedStruct/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStaticSpecifierOnAnonymousNamespaceMember/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppStringLiteralToCharPointerConversion/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppSyntaxWarning/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTabsAreDisallowed/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateArgumentsCanBeDeduced/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterNeverUsed/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTemplateParameterShadowing/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppThrowExpressionCanBeReplacedWithRethrow/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScope/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTooWideScopeInitStatement/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppTypeAliasNeverUsed/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedDependentBaseClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUninitializedNonStaticDataMember/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnionMemberOfReferenceType/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaEndRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnmatchedPragmaRegionDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnamedNamespaceInHeaderFile/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnnecessaryWhitespace/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnreachableCode/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnsignedZeroComparison/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUnusedIncludeDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAlgorithmWithCount/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAssociativeContains/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAuto/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseAutoForNumeric/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseDesignatedInitializers/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseElementsView/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseEraseAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseFamiliarTemplateSyntaxForGenericLambdas/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseInternalLinkage/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseRangeAlgorithm/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStdSize/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseStructuredBinding/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUseTypeTraitAlias/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUserDefinedLiteralSuffixDoesNotStartWithUnderscore/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppUsingResultOfAssignmentAsCondition/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVariableCanBeMadeConstexpr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionCallInsideCtor/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVirtualFunctionInFinalClass/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppVolatileParameterInDeclaration/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWarningDirective/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongIncludesOrder/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppWrongSlashesInIncludeDirective/@EntryIndexedValue" value="HINT" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroConstantCanBeReplacedWithNullptr/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=CppZeroValuedExpressionUsedAsNullPointer/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IdentifierTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=IfStdIsConstantEvaluatedCanBeReplaced/@EntryIndexedValue" value="SUGGESTION" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StdIsConstantEvaluatedWillAlwaysEvaluateToConstant/@EntryIndexedValue" value="WARNING" type="string" />
<option name="/Default/CodeInspection/Highlighting/InspectionSeverities/=StringLiteralTypo/@EntryIndexedValue" value="DO_NOT_SHOW" type="string" />
</component>
</project>
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 acdry contributors
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.
@@ -4,17 +4,36 @@
classes = {};
objectVersion = 56;
objects = {
F00000000000000000000001 = {isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 6; files = (F00000000000000000000004); name = "Embed CLI"; runOnlyForDeploymentPostprocessing = 0; };
F00000000000000000000002 = {isa = PBXTargetDependency; target = B00000000000000000000002; targetProxy = F00000000000000000000003; };
F00000000000000000000003 = {isa = PBXContainerItemProxy; containerPortal = A00000000000000000000001; proxyType = 1; remoteGlobalIDString = B00000000000000000000002; remoteInfo = "container-compose"; };
F00000000000000000000004 = {isa = PBXBuildFile; fileRef = B00000000000000000000008; };
C00000000000000000000000 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MonitorUI.swift"; sourceTree = "<group>"; };
D00000000000000000000000 = {isa = PBXBuildFile; fileRef = C00000000000000000000000; };
C00000000000000000000001 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MonitorCLI.swift"; sourceTree = "<group>"; };
E00000000000000000000001 = {isa = PBXBuildFile; fileRef = C00000000000000000000001; };
C00000000000000000000002 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MonitorCore/ContainerService.swift"; sourceTree = "<group>"; };
D00000000000000000000002 = {isa = PBXBuildFile; fileRef = C00000000000000000000002; };
E00000000000000000000002 = {isa = PBXBuildFile; fileRef = C00000000000000000000002; };
C00000000000000000000003 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MonitorCore/NetworkRateTracker.swift"; sourceTree = "<group>"; };
D00000000000000000000003 = {isa = PBXBuildFile; fileRef = C00000000000000000000003; };
E00000000000000000000003 = {isa = PBXBuildFile; fileRef = C00000000000000000000003; };
C00000000000000000000004 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MonitorCore/VolumeInventory.swift"; sourceTree = "<group>"; };
D00000000000000000000004 = {isa = PBXBuildFile; fileRef = C00000000000000000000004; };
E00000000000000000000004 = {isa = PBXBuildFile; fileRef = C00000000000000000000004; };
A00000000000000000000001 = {isa = PBXProject; attributes = {LastUpgradeCheck = 1600; TargetAttributes = {A00000000000000000000002 = {CreatedOnToolsVersion = 16.0; }; }; }; buildConfigurationList = A00000000000000000000010; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = (en, Base); mainGroup = A00000000000000000000003; packageReferences = (A00000000000000000000020); productRefGroup = A00000000000000000000004; projectDirPath = ""; projectRoot = ""; targets = (A00000000000000000000002, B00000000000000000000002); };
A00000000000000000000002 = {isa = PBXNativeTarget; buildConfigurationList = A00000000000000000000011; buildPhases = (A00000000000000000000005, A00000000000000000000006, A00000000000000000000007); buildRules = (); dependencies = (); name = AppleContainerCompose; packageProductDependencies = (A00000000000000000000021); productName = AppleContainerCompose; productReference = A00000000000000000000008; productType = "com.apple.product-type.application"; };
A00000000000000000000002 = {isa = PBXNativeTarget; buildConfigurationList = A00000000000000000000011; buildPhases = (A00000000000000000000005, A00000000000000000000006, A00000000000000000000007, F00000000000000000000001); buildRules = (); dependencies = (F00000000000000000000002); name = ContainerCompose; packageProductDependencies = (A00000000000000000000021); productName = ContainerCompose; productReference = A00000000000000000000008; productType = "com.apple.product-type.application"; };
A00000000000000000000003 = {isa = PBXGroup; children = (A00000000000000000000009, A00000000000000000000004); sourceTree = "<group>"; };
A00000000000000000000004 = {isa = PBXGroup; children = (A00000000000000000000008, B00000000000000000000008); name = Products; sourceTree = "<group>"; };
A00000000000000000000009 = {isa = PBXGroup; children = (A00000000000000000000030, A00000000000000000000031, A00000000000000000000032, A00000000000000000000034, A00000000000000000000035); path = Sources; sourceTree = "<group>"; };
A00000000000000000000009 = {isa = PBXGroup; children = (C00000000000000000000000, C00000000000000000000001, C00000000000000000000002, C00000000000000000000003, C00000000000000000000004, A00000000000000000000030, A00000000000000000000031, A00000000000000000000032, A00000000000000000000034, A00000000000000000000035); path = Sources; sourceTree = "<group>"; };
A00000000000000000000030 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = "<group>"; };
A00000000000000000000031 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeModel.swift; sourceTree = "<group>"; };
A00000000000000000000032 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
A00000000000000000000034 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeCore.swift; sourceTree = "<group>"; };
A00000000000000000000035 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLI.swift; sourceTree = "<group>"; };
A00000000000000000000008 = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppleContainerCompose.app; sourceTree = BUILT_PRODUCTS_DIR; };
A00000000000000000000008 = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Container Compose.app"; sourceTree = BUILT_PRODUCTS_DIR; };
A00000000000000000000040 = {isa = PBXBuildFile; fileRef = A00000000000000000000030; };
A00000000000000000000041 = {isa = PBXBuildFile; fileRef = A00000000000000000000031; };
A00000000000000000000042 = {isa = PBXBuildFile; fileRef = A00000000000000000000032; };
@@ -23,22 +42,22 @@
B00000000000000000000044 = {isa = PBXBuildFile; fileRef = A00000000000000000000035; };
B00000000000000000000045 = {isa = PBXBuildFile; productRef = A00000000000000000000021; };
A00000000000000000000044 = {isa = PBXBuildFile; productRef = A00000000000000000000021; };
A00000000000000000000005 = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (A00000000000000000000040, A00000000000000000000041, A00000000000000000000042, A00000000000000000000043); runOnlyForDeploymentPostprocessing = 0; };
A00000000000000000000005 = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (D00000000000000000000000, D00000000000000000000002, D00000000000000000000003, D00000000000000000000004, A00000000000000000000040, A00000000000000000000041, A00000000000000000000042, A00000000000000000000043); runOnlyForDeploymentPostprocessing = 0; };
A00000000000000000000006 = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (A00000000000000000000044); runOnlyForDeploymentPostprocessing = 0; };
A00000000000000000000007 = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
A00000000000000000000010 = {isa = XCConfigurationList; buildConfigurations = (A00000000000000000000012, A00000000000000000000013); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; };
A00000000000000000000011 = {isa = XCConfigurationList; buildConfigurations = (A00000000000000000000014, A00000000000000000000015); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; };
A00000000000000000000012 = {isa = XCBuildConfiguration; buildSettings = {CLANG_ENABLE_MODULES = YES; MACOSX_DEPLOYMENT_TARGET = 26.0; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; name = Debug; };
A00000000000000000000013 = {isa = XCBuildConfiguration; buildSettings = {CLANG_ENABLE_MODULES = YES; MACOSX_DEPLOYMENT_TARGET = 26.0; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; name = Release; };
A00000000000000000000014 = {isa = XCBuildConfiguration; buildSettings = {ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "Apple Container Compose"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 0.1.0; PRODUCT_BUNDLE_IDENTIFIER = net.absdev.AppleContainerCompose; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; ENABLE_APP_SANDBOX = NO; }; name = Debug; };
A00000000000000000000015 = {isa = XCBuildConfiguration; buildSettings = {ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "Apple Container Compose"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 0.1.0; PRODUCT_BUNDLE_IDENTIFIER = net.absdev.AppleContainerCompose; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; ENABLE_APP_SANDBOX = NO; }; name = Release; };
B00000000000000000000002 = {isa = PBXNativeTarget; buildConfigurationList = B00000000000000000000011; buildPhases = (B00000000000000000000005, B00000000000000000000006); buildRules = (); dependencies = (); name = accompose; packageProductDependencies = (A00000000000000000000021); productName = accompose; productReference = B00000000000000000000008; productType = "com.apple.product-type.tool"; };
B00000000000000000000008 = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = accompose; sourceTree = BUILT_PRODUCTS_DIR; };
B00000000000000000000005 = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (B00000000000000000000043, B00000000000000000000044); runOnlyForDeploymentPostprocessing = 0; };
A00000000000000000000014 = {isa = XCBuildConfiguration; buildSettings = {ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "Container Compose"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 0.2.0; PRODUCT_BUNDLE_IDENTIFIER = net.absdev.ContainerCompose; PRODUCT_NAME = "Container Compose"; SWIFT_VERSION = 5.0; ENABLE_APP_SANDBOX = NO; }; name = Debug; };
A00000000000000000000015 = {isa = XCBuildConfiguration; buildSettings = {ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_CFBundleDisplayName = "Container Compose"; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; MACOSX_DEPLOYMENT_TARGET = 26.0; MARKETING_VERSION = 0.2.0; PRODUCT_BUNDLE_IDENTIFIER = net.absdev.ContainerCompose; PRODUCT_NAME = "Container Compose"; SWIFT_VERSION = 5.0; ENABLE_APP_SANDBOX = NO; }; name = Release; };
B00000000000000000000002 = {isa = PBXNativeTarget; buildConfigurationList = B00000000000000000000011; buildPhases = (B00000000000000000000005, B00000000000000000000006); buildRules = (); dependencies = (); name = "container-compose"; packageProductDependencies = (A00000000000000000000021); productName = "container-compose"; productReference = B00000000000000000000008; productType = "com.apple.product-type.tool"; };
B00000000000000000000008 = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "container-compose"; sourceTree = BUILT_PRODUCTS_DIR; };
B00000000000000000000005 = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (E00000000000000000000001, E00000000000000000000002, E00000000000000000000003, E00000000000000000000004, B00000000000000000000043, B00000000000000000000044); runOnlyForDeploymentPostprocessing = 0; };
B00000000000000000000006 = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (B00000000000000000000045); runOnlyForDeploymentPostprocessing = 0; };
B00000000000000000000011 = {isa = XCConfigurationList; buildConfigurations = (B00000000000000000000014, B00000000000000000000015); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; };
B00000000000000000000014 = {isa = XCBuildConfiguration; buildSettings = {CODE_SIGNING_ALLOWED = NO; MACOSX_DEPLOYMENT_TARGET = 26.0; PRODUCT_NAME = accompose; SWIFT_VERSION = 5.0; }; name = Debug; };
B00000000000000000000015 = {isa = XCBuildConfiguration; buildSettings = {CODE_SIGNING_ALLOWED = NO; MACOSX_DEPLOYMENT_TARGET = 26.0; PRODUCT_NAME = accompose; SWIFT_VERSION = 5.0; }; name = Release; };
B00000000000000000000014 = {isa = XCBuildConfiguration; buildSettings = {CODE_SIGNING_ALLOWED = NO; MACOSX_DEPLOYMENT_TARGET = 26.0; PRODUCT_NAME = "container-compose"; SWIFT_VERSION = 5.0; }; name = Debug; };
B00000000000000000000015 = {isa = XCBuildConfiguration; buildSettings = {CODE_SIGNING_ALLOWED = NO; MACOSX_DEPLOYMENT_TARGET = 26.0; PRODUCT_NAME = "container-compose"; SWIFT_VERSION = 5.0; }; name = Release; };
A00000000000000000000020 = {isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/jpsim/Yams.git"; requirement = {kind = upToNextMajorVersion; minimumVersion = 5.1.3; }; };
A00000000000000000000021 = {isa = XCSwiftPackageProductDependency; package = A00000000000000000000020; productName = Yams; };
};
+8 -37
View File
@@ -1,41 +1,12 @@
name: laravel
name: demo
services:
web:
build:
context: ./docker/php
args:
uid: ${UID:-1000}
gid: ${GID:-1000}
depends_on:
- db
volumes:
- ./:/home/devuser/app
- ./storage/tmp/composer:/home/devuser/.composer/cache
- ./storage/tmp/npm:/home/devuser/.npm
image: docker.io/library/nginx:alpine
ports:
- ${WORKSPACE_PORT:-8080}:8080
restart: always
db:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=root_secret
- MYSQL_DATABASE=zeus
- MYSQL_USER=zeus_user
- MYSQL_PASSWORD=secret
- "8080:80"
volumes:
- ./storage/tmp/db:/var/lib/mysql
restart: always
adminer:
image: adminer
restart: always
ports:
- 9000:8080
nginx:
build: ./docker/nginx
ports:
- ${NGINX_PORT:-80}:80
volumes:
- ./:/home/devuser/app
depends_on:
- web
restart: always
- ./site:/usr/share/nginx/html:ro
networks:
- frontend
networks:
frontend: {}
-12
View File
@@ -1,12 +0,0 @@
name: demo
services:
web:
image: docker.io/library/nginx:alpine
ports:
- "8080:80"
volumes:
- ./site:/usr/share/nginx/html:ro
networks:
- frontend
networks:
frontend: {}
+9
View File
@@ -0,0 +1,9 @@
// swift-tools-version: 6.0
import PackageDescription
// The complete app and CLI targets are in ContainerCompose.xcodeproj.
// This package runs the recovered acdry core's focused regression suite.
let package = Package(name: "ContainerComposeMonitorTests", platforms: [.macOS(.v14)],
targets: [
.target(name: "ACDryCore", path: "Sources/MonitorCore"),
.testTarget(name: "ACDryCoreTests", dependencies: ["ACDryCore"], path: "Tests/MonitorCoreTests")
], swiftLanguageModes: [.v5])
+38 -77
View File
@@ -1,96 +1,57 @@
# AppleContainerCompose — Native Xcode project (developer preview)
# Container Compose 0.2.0
A SwiftUI macOS application and **native Swift** `accompose` CLI that operate Apple's `container` runtime. No Python, PyYAML, ServBay, Docker Desktop, or Docker daemon is needed. Both targets compile the same `ComposeCore.swift` orchestration implementation and use the Yams Swift package for YAML parsing.
One native macOS Xcode app combining the existing AppleContainerCompose orchestration engine and the supplied **ACDry Swift 1.1.0** monitor, with one **container-compose** CLI. Requires Apple silicon, macOS 26+, Xcode 26+ and Apple's `container` runtime. No Docker daemon, Go or Python is required to run it. Xcode fetches Yams for YAML parsing; Python 3 is used only by CLI tests.
## Requirements
## Build
- Apple silicon, macOS 26+, Xcode 26+
- Apple's [`container`](https://github.com/apple/container) CLI installed, with a configured kernel (`container system start`)
- Internet on first build to fetch the Yams Swift package
Open `ContainerCompose.xcodeproj`, choose **ContainerCompose**, and Run. The **Container Compose.app** product includes `container-compose` in Contents/MacOS. Choose **Save Command-Line Tool…** in the application menu to export it. Alternatively run `./Scripts/install-cli.sh` to build and install the CLI into `/opt/homebrew/bin`; override `PREFIX` if desired. The installer uses a fresh temporary build directory and preserves old accompose/acdry installations.
## Build and run GUI
The **Compose** tab opens existing compose.yaml/docker-compose.yml files and exposes Up, Down, Restart, Status, Logs, Pull, Build and runtime diagnostics. GUI Up is detached. The **Monitor & Resources** tab supplies the acdry dashboard, Containers, Images, Networks, Volumes and Projects. Opening YAML supplies its project name to monitoring. Resource lists remain runtime-wide unless the Projects filter applies. The executable selected in Settings is shared by both GUI engines. CLI discovery uses PATH and Homebrew fallback locations.
Open `AppleContainerCompose.xcodeproj`, select the `AppleContainerCompose` scheme and run. Use **Open YAML** to load `Examples/compose.yaml`, then Up / Down / Status / Logs. The GUI invokes the same native Swift engine as the CLI, not Python.
Monitor features retained: CPU/memory/network charts, six-minute in-memory history, measured receive/transmit rates, freshness indicators, searchable resource lists, inspect, polling logs with follow, stats, processes, verified lifecycle actions, explicit deletion/prune confirmations, mount references and inspect-only host mounts, keyboard shortcuts, and read-only demo mode. Disk read/write totals appear in container lists and Stats when the runtime provides them. Volume capacity is provisioned size, not occupied space. Process inspection needs `ps` inside the image. There is no fabricated live telemetry when metrics are absent.
## Install command line utility
From Terminal, in the project root:
```bash
./Scripts/install-cli.sh
accompose doctor
cd Examples
accompose up -d
accompose ps
accompose logs
accompose down
```
The installer builds the `accompose` Xcode scheme in Release and copies the binary to `/opt/homebrew/bin/accompose` (override `PREFIX=/your/prefix`). It asks for administrator permissions only if the destination directory is not writable. Add the prefix's `bin` directory to your PATH if needed.
## CLI usage
```
accompose init
accompose [-f compose.yaml] [-p project] config
accompose [-v|-vv|-vvv] up [-d|--detach] [--build]
accompose down [--volumes]
accompose ps
accompose pull
accompose build
accompose logs [service]
accompose start|stop|restart [service]
accompose exec SERVICE COMMAND [ARG ...]
accompose doctor
```
The default file discovery order is `compose.yaml`, `compose.yml`, `docker-compose.yaml`, `docker-compose.yml`. `.env` substitution, build contexts, Dockerfiles, service dependency ordering, environment variables, published ports, bind mounts, named volumes, and custom networks have initial support. Names are scoped by project.
## Limitations / important safety notes
This is an **early developer preview**, not a drop-in Docker Compose implementation. Only a subset of Compose YAML is parsed. Long-form mounts/ports, secrets, service health gating, robust reconciliation, profiles, scaling, live `logs -f`, restart supervision, and full network aliases are **not implemented**. A service with multiple networks only attaches to its first network; the CLI prints a warning. Restart policies are parsed but **not enforced**. `up` does not update an existing container when YAML changes: use `down` then `up`. `ps` currently lists all Apple containers, not just the project. Named volumes are not removed by `down` unless `--volumes` is provided. `down` may stop/remove project containers: check the project name before running it. The installer never automatically downloads or configures an unverified kernel.
Debug and Release builds of both Xcode targets and real Apple container runtime checks passed on macOS. See `Validation/VALIDATION.md` for the environment, checks and limitations.
## Updated developer preview: CLI rename and cleanup
The CLI target and installer are now named `accompose`. Re-run `./Scripts/install-cli.sh` after replacing your old project. The older `acccompose` executable is not automatically deleted. Verify using `accompose --help`.
Additional commands:
## CLI
```sh
accompose import ./docker-compose.yml # validate existing YAML without modifying it
accompose import ~/Projects/example # validate directory's default Compose file
accompose clean web --dry-run # preview deletion
accompose clean web # delete managed service container
accompose clean --all --dry-run # preview project container deletion
accompose clean --all # delete project containers
accompose prune --images # request runtime unused-image cleanup
container-compose --help
container-compose doctor
container-compose -f compose.yaml config
container-compose -vvv up -d # returns after startup
container-compose up # streams logs; Ctrl+C gracefully stops services
container-compose up --build -d
container-compose logs [SERVICE] # snapshot
container-compose start|stop|restart [SERVICE]
container-compose exec web sh # interactive terminal when invoked from a terminal
container-compose down # preserves named volumes
container-compose down --volumes
container-compose clean --all --dry-run
container-compose import ./docker-compose.yml
container-compose monitor --help
container-compose monitor watch
container-compose monitor --demo watch --json --count 2
container-compose monitor stats
container-compose monitor logs --follow CONTAINER
container-compose monitor volumes --all-mounts
container-compose monitor volume create -s 1G data
container-compose monitor projects
```
The sample `Examples/site/index.html` is included to prevent the original Nginx bind mount failure. Missing directory bind sources are created during `up`; do not use a missing *file* bind source because it will be created as a directory.
The monitor namespace retains acdry's CLI commands, including resource inspection, lifecycle, image/network/volume commands, live terminal monitoring and streaming logs. Its global options (`--container PATH`, `--project NAME`, `--demo`) precede its subcommand. Compose service commands and monitor container-ID commands are separate; use `monitor --help` for exact syntax. Resource prune in this namespace requires `--yes` and affects all unused resources of the chosen type.
### Compatibility limitations
`up -d`/`--detach` and verbosity `-v`/`-vv`/`-vvv` remain independent; options work before/after up. Foreground SIGINT/SIGTERM stops reached services in reverse dependency order and preserves containers. A log follower error is reported, and may leave containers running. Existing stopped containers are started; changed configuration requires down/up. Concurrent external runtime changes are not reconciled. Multiple foreground service logs share the terminal without prefixes.
This is **not** full Docker Compose support. Import validates only the subset of YAML handled by ComposeCore. Unrecognized service keys cause an explicit error rather than being silently ignored. Import does not yet persist projects to a GUI library, multi-file merging and YAML extension semantics are incomplete, and clean only deletes managed project containers (not host data, volumes, images or networks). Image pruning relies on the installed Apple `container` version supporting `container image prune`. The kernel and all host bind files are preserved. `--dry-run` is only supported for clean and prune. The detached/foreground lifecycle has been integration-tested on the runtime version recorded in `Validation/VALIDATION.md`.
## Compose compatibility and gaps
This is a developer preview with **partial Compose support**, not Docker Compose parity. Supported: YAML service images, Dockerfile build contexts/dockerfile/build args, short ports and volume mounts, environment list/mapping, `.env`/`${VAR}` interpolation, command, working_dir, dependency ordering and project-scoped names. Empty named-volume and custom-network declarations, plus explicit default local/bridge drivers, are supported. YAML anchors handled by Yams may work, but Compose extension/merge semantics are not implemented. Treat config as validation plus original YAML, not canonical expanded Docker Compose output.
## Foreground and detached mode fix
Unimplemented in this app: profiles, scaling, multi-file merge, include/extends, env_file, healthchecks and health/completion dependency gating, secrets/configs, deploy, watch/rebuild reconciliation, wait, one-off run, cp, events, publish/push, robust Docker shell parsing/interpolation, long port/mount syntax, network attachment options/aliases, external/custom resource names and driver/IPAM configuration. These require further implementation; they are not all Apple runtime limitations. Unknown service/root fields and several unsupported nested options are rejected before runtime changes. Restart policies only produce a warning and have no supervisor. Multiple service networks only attach the first, with a warning. `ps` lists the whole runtime. Compose `logs` is a snapshot; use `monitor logs --follow ID` for streaming outside foreground up.
- `accompose up` streams each service's logs live and stays attached until log followers finish or the command is interrupted.
- `accompose up -d` and `accompose up --detach` return after startup, leaving containers running.
- `-v`, `-vv` and `-vvv` change diagnostics only. They work before or after `up`, for example `accompose -vvv up -d` and `accompose up -d -vvv`.
- Ctrl+C (or SIGTERM) in foreground mode stops managed containers in reverse dependency order using the runtime's graceful `stop`; containers are preserved for subsequent `up`. Interrupted startup stops the containers reached so far.
- Existing stopped containers are restarted. Configuration changes still require `down` and `up`.
- The GUI's Up action retains its asynchronous, detached engine behavior.
- The runtime executable is resolved from PATH first, with Homebrew/system fallback paths.
Apple-runtime differences: this app uses Apple's CLI and per-container Linux VMs, not Docker Engine's API/socket; Docker-socket-dependent tools and Docker Swarm features are not provided. Runtime flags, networking/DNS, image architecture and storage behavior differ from Docker. Do not assume bare Compose service-name DNS works. See Apple's [command reference](https://github.com/apple/container/blob/main/docs/command-reference.md), [technical overview](https://github.com/apple/container/blob/main/docs/technical-overview.md) and [networking guide](https://github.com/apple/container/blob/main/docs/networking.md). Runtime-supported features still need explicit translation in this app.
Verbosity levels add mode/project details, Compose file location, then startup order. They do not change container execution mode. `logs` remains a snapshot command; foreground `up` uses independent live log followers. Multiple service logs share the terminal without added service prefixes.
Bind mounts can create a missing source as a directory; create file bind sources yourself first. Down/clean remove managed containers based on project/service names; verify project names before use. Down --volumes deletes project named-volume data. Host mount files are never deleted by cleanup. Build/install does not automatically start or configure the runtime/kernel.
To rerun the isolated regression suite (Python 3 is needed only for tests):
## Source provenance and validation
```sh
python3 Tests/cli_regression.py /absolute/path/to/built/accompose
```
Compose source: local AppleContainerCompose-Laravel-Fixed, matching the development checkout's ComposeCore. Monitor source: uploaded `ACDry-Swift-1.1.0.zip`, whose 14 source-package files matched the recovered local 1.1.0 release byte-for-byte before integration. The earlier acdry.zip contained an empty README and Git metadata only. Original projects and archives were preserved. The recovered monitor's MIT license is in ACDry-LICENSE. No upstream Dry Go code is included.
The suite substitutes a temporary fake runtime and never changes real containers.
See VALIDATION.md for results and reproduction. The Xcode project is the complete app/CLI build. Package.swift exposes only the monitor-core regression tests. The deliverable is source; local builds require your own signing/notarization for distribution.
+11 -7
View File
@@ -5,17 +5,21 @@ PREFIX="${PREFIX:-/opt/homebrew}"
if ! command -v xcodebuild >/dev/null 2>&1; then
echo 'Xcode command-line tools / Xcode required.' >&2; exit 1
fi
xcodebuild -project "$ROOT/AppleContainerCompose.xcodeproj" -scheme accompose \
-configuration Release -derivedDataPath "$ROOT/.build" CODE_SIGNING_ALLOWED=NO build
BIN="$ROOT/.build/Build/Products/Release/accompose"
TEMP_ROOT="${TMPDIR:-/private/tmp}"
if [ -d /Volumes/ChatGPT ]; then TEMP_ROOT=/Volumes/ChatGPT; fi
BUILD_DIR="$(mktemp -d "$TEMP_ROOT/container-compose-install.XXXXXX")"
trap 'rm -rf "$BUILD_DIR"' EXIT
xcodebuild -project "$ROOT/ContainerCompose.xcodeproj" -scheme container-compose \
-configuration Release -derivedDataPath "$BUILD_DIR" CODE_SIGNING_ALLOWED=NO build
BIN="$BUILD_DIR/Build/Products/Release/container-compose"
if [ ! -x "$BIN" ]; then
echo "CLI executable not found at $BIN" >&2; exit 1
fi
mkdir -p "$PREFIX/bin" 2>/dev/null || sudo mkdir -p "$PREFIX/bin"
if [ -w "$PREFIX/bin" ]; then
install -m 755 "$BIN" "$PREFIX/bin/accompose"
install -m 755 "$BIN" "$PREFIX/bin/container-compose"
else
sudo install -m 755 "$BIN" "$PREFIX/bin/accompose"
sudo install -m 755 "$BIN" "$PREFIX/bin/container-compose"
fi
printf 'Installed: %s/bin/accompose\n' "$PREFIX"
"$PREFIX/bin/accompose" --help
printf 'Installed: %s/bin/container-compose\n' "$PREFIX"
"$PREFIX/bin/container-compose" --help
+24 -4
View File
@@ -1,10 +1,30 @@
import SwiftUI
@main
struct AppleContainerComposeApp: App {
@StateObject private var model = ComposeModel()
struct ContainerComposeApp: App {
@StateObject private var compose = ComposeModel()
@StateObject private var monitor = Monitor()
@State private var tab = "compose"
var body: some Scene {
WindowGroup("Apple Container Compose") { ContentView().environmentObject(model) }
.defaultSize(width: 1050, height: 720)
WindowGroup("Container Compose") {
TabView(selection: $tab) {
ContentView().environmentObject(compose).disabled(monitor.demo || monitor.acting)
.tabItem { Label("Compose", systemImage: "square.stack.3d.up") }.tag("compose")
Workspace(model: monitor).disabled(compose.busy)
.tabItem { Label("Monitor & Resources", systemImage: "waveform.path.ecg") }.tag("monitor")
}
.frame(minWidth: 1050, minHeight: 720)
.onChange(of: compose.projectName) { _, name in monitor.project = name }
}.defaultSize(width: 1250, height: 850)
.commands {
CommandGroup(after: .appInfo) { Button("Save Command-Line Tool…") { monitor.exportCLI() } }
CommandMenu("Containers") {
Button("Refresh") { Task { await monitor.refresh() } }.keyboardShortcut("r")
Button("Inspect") { monitor.mode = "Inspect" }.keyboardShortcut("i")
Button("Logs") { monitor.mode = "Logs" }.keyboardShortcut("l")
Button("Stats") { monitor.mode = "Stats" }.keyboardShortcut("s")
}
}
Settings { Preferences(model: monitor) }
}
}
+27 -7
View File
@@ -2,17 +2,23 @@ import Foundation
@main
struct AppleComposeCLI {
static func main() {
do { try execute() }
catch { fputs("accompose: \(error)\n", stderr); exit(1) }
static func main() async {
do {
let args = Array(CommandLine.arguments.dropFirst())
if args.first == "monitor" { try await MonitorCLI.run(Array(args.dropFirst())); return }
try execute()
}
catch { fputs("container-compose: \(error)\n", stderr); exit(1) }
}
static func execute() throws {
var arguments = Array(CommandLine.arguments.dropFirst())
let help = """
accompose — Apple Container Compose CLI
Usage: accompose [-f FILE] [-p PROJECT] COMMAND [OPTIONS]
container-compose — Container Compose CLI
Usage: container-compose [-f FILE] [-p PROJECT] COMMAND [OPTIONS]
Commands:
monitor [COMMAND] acdry resource manager; monitor --help for commands
monitor watch Live CPU, memory and network terminal dashboard
init Create a starter compose.yaml
doctor Check Apple Container runtime
config Validate Compose file and display configuration
@@ -36,6 +42,7 @@ struct AppleComposeCLI {
-f, --file FILE Compose file path
-p, --project-name N Override project name
-v, -vv, -vvv Increase diagnostics (independent of detach)
--version Display CLI version
-h, --help Display help
Supported: basic images, Dockerfile builds, ports, directory binds,
@@ -43,6 +50,7 @@ struct AppleComposeCLI {
Other service keys are rejected instead of silently ignored.
This is NOT a complete Docker Compose implementation.
"""
if arguments == ["--version"] { print("container-compose 0.2.0 (Compose + acdry native monitor)"); return }
if arguments.contains("--help") || arguments.contains("-h") || arguments.isEmpty {
print(help)
return
@@ -66,7 +74,7 @@ struct AppleComposeCLI {
}
guard let command = arguments.first else { throw ComposeError.message("Missing command") }
if command == "doctor" && file == nil && (try? ComposeProject.discover()) == nil {
print("Apple Container Compose diagnostics")
print("Container Compose diagnostics")
guard let binary = Shell.executable("container") else { throw ComposeError.message("Apple container CLI not found. Install from https://github.com/apple/container/releases") }
print("CLI: \(binary)")
print(try Shell.checked("container", ["--version"]))
@@ -76,7 +84,7 @@ struct AppleComposeCLI {
return
}
if command == "import" {
guard arguments.count >= 2 else { throw ComposeError.message("Usage: accompose import PATH") }
guard arguments.count >= 2 else { throw ComposeError.message("Usage: container-compose import PATH") }
let url = URL(fileURLWithPath: arguments[1]).standardizedFileURL
var isDir: ObjCBool = false
guard FileManager.default.fileExists(atPath: url.path, isDirectory: &isDir) else { throw ComposeError.message("Path not found: \(url.path)") }
@@ -97,6 +105,18 @@ struct AppleComposeCLI {
print("Created \(target.path)"); return
}
let commandArgs = Array(arguments.dropFirst())
let allowed: [String: Set<String>] = [
"up": ["-d", "--detach", "--build"], "down": ["--volumes"],
"clean": ["--all", "--project", "--dry-run"], "prune": ["--images", "--dry-run"],
"config": [], "ps": [], "pull": [], "build": [], "logs": [],
"start": [], "stop": [], "restart": [], "doctor": []
]
if let options = allowed[command], let unknown = commandArgs.first(where: { $0.hasPrefix("-") && !options.contains($0) }) {
throw ComposeError.message("Unsupported \(command) option: \(unknown)")
}
if ["config", "ps", "pull", "build", "down", "doctor", "prune"].contains(command), commandArgs.contains(where: { !$0.hasPrefix("-") }) {
throw ComposeError.message("Unexpected argument for \(command)")
}
if command == "up" {
let supported: Set<String> = ["-d", "--detach", "--build"]
if let unknown = commandArgs.first(where: { !supported.contains($0) }) {
+97 -9
View File
@@ -33,6 +33,7 @@ public struct ComposeService: Identifiable {
public var image: String
public var buildContext: String?
public var dockerfile: String?
public var buildArguments: [String]
public var ports: [String]
public var volumes: [String]
public var environment: [String]
@@ -55,6 +56,10 @@ public struct CommandResult {
public enum Shell {
public static func executable(_ name: String) -> String? {
if name == "container", Bundle.main.bundleURL.pathExtension == "app",
let chosen = UserDefaults.standard.string(forKey: "containerBinary"), !chosen.isEmpty {
return FileManager.default.isExecutableFile(atPath: chosen) ? chosen : nil
}
let paths = (ProcessInfo.processInfo.environment["PATH"] ?? "").split(separator: ":").map(String.init) + ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin", "/bin"]
return paths.map { ($0 as NSString).appendingPathComponent(name) }.first { FileManager.default.isExecutableFile(atPath: $0) }
}
@@ -96,7 +101,7 @@ public struct ComposeProject {
let url = URL(fileURLWithPath: manager.currentDirectoryPath).appendingPathComponent(name)
if manager.fileExists(atPath: url.path) { return url }
}
throw ComposeError.message("No compose.yaml found. Use -f FILE or run accompose init.")
throw ComposeError.message("No compose.yaml found. Use -f FILE or run container-compose init.")
}
public init(file: URL, overrideName: String? = nil) throws {
self.file = file
@@ -111,9 +116,39 @@ public struct ComposeProject {
if parts.count == 2 && vars[parts[0]] == nil { vars[parts[0]] = parts[1].trimmingCharacters(in: CharacterSet(charactersIn: "\"'")) }
}
let expanded = try Self.interpolate(source, vars: vars)
guard let root = try Yams.load(yaml: expanded) as? [String: Any], let rawServices = root["services"] as? [String: Any], !rawServices.isEmpty else { throw ComposeError.message("Compose file must contain a nonempty services mapping") }
guard !source.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
throw ComposeError.message("Compose file is empty: \(file.path). Save your YAML to this file or select another file with -f.")
}
let loaded: Any?
do { loaded = try Yams.load(yaml: expanded) }
catch { throw ComposeError.message("Invalid YAML in \(file.path): \(error)") }
guard let root = loaded as? [String: Any] else {
throw ComposeError.message("Compose file must have a mapping at its root: \(file.path)")
}
guard let servicesValue = root["services"] else {
throw ComposeError.message("Missing top-level services mapping in \(file.path). Top-level keys: \(root.keys.sorted().joined(separator: ", ")). Check the filename and indentation.")
}
guard let rawServices = servicesValue as? [String: Any], !rawServices.isEmpty else {
throw ComposeError.message("The services value must be a nonempty mapping in \(file.path). Check YAML indentation.")
}
let unknownRoot = Set(root.keys).subtracting(["name", "version", "services", "networks", "volumes"]).filter { !$0.hasPrefix("x-") }
guard unknownRoot.isEmpty else { throw ComposeError.message("Unsupported Compose fields: \(unknownRoot.sorted().joined(separator: ", "))") }
for kind in ["networks", "volumes"] {
if let value = root[kind] {
guard let entries = value as? [String: Any] else { throw ComposeError.message("\(kind) must be a mapping") }
for (name, options) in entries {
let mapping = options as? [String: Any]
let defaultDriver = kind == "networks" ? "bridge" : "local"
let simpleDefault = mapping.map { Set($0.keys).isSubset(of: ["driver"]) && ($0["driver"] == nil || $0["driver"] as? String == defaultDriver) } ?? false
guard options is NSNull || simpleDefault else {
throw ComposeError.message("Unsupported \(kind).\(name) options (external, custom driver, name and IPAM semantics are not implemented)")
}
}
}
}
let requestedName = overrideName ?? (root["name"] as? String) ?? file.deletingLastPathComponent().lastPathComponent
self.name = Self.safeName(requestedName)
guard !self.name.isEmpty else { throw ComposeError.message("Project name must contain an ASCII letter or digit") }
self.networks = (root["networks"] as? [String: Any]).map { Array($0.keys).sorted() } ?? []
self.volumes = (root["volumes"] as? [String: Any]).map { Array($0.keys).sorted() } ?? []
let resolvedProjectName = self.name
@@ -124,9 +159,50 @@ public struct ComposeProject {
if !ignored.isEmpty {
throw ComposeError.message("Service \(key) contains options not yet supported by Apple Container: \(ignored.sorted().joined(separator: ", ")). Refusing to silently ignore them.")
}
for field in ["ports", "volumes"] {
if let value = raw[field] {
guard let list = value as? [Any], list.allSatisfy({ $0 is String || (field == "ports" && $0 is Int) }) else {
throw ComposeError.message("Service \(key) \(field) supports short scalar syntax only; long syntax is not implemented")
}
}
}
if let networks = raw["networks"] as? [String: Any] {
guard networks.values.allSatisfy({ $0 is NSNull || ($0 as? [String: Any])?.isEmpty == true }) else {
throw ComposeError.message("Service \(key) network aliases and attachment options are not implemented")
}
}
if let dependencies = raw["depends_on"] as? [String: Any] {
for (_, value) in dependencies {
guard let options = value as? [String: Any], Set(options.keys).isSubset(of: ["condition"]), options["condition"] as? String == "service_started" else {
throw ComposeError.message("Service \(key) supports depends_on service_started only; health/completion gating is not implemented")
}
}
}
let build = raw["build"]
let buildMap = build as? [String: Any]
if let buildMap {
let unknown = Set(buildMap.keys).subtracting(["context", "dockerfile", "args"])
guard unknown.isEmpty else { throw ComposeError.message("Unsupported build options for \(key): \(unknown.sorted().joined(separator: ", "))") }
}
let context = (build as? String) ?? (buildMap?["context"] as? String)
var buildArguments: [String] = []
if let value = buildMap?["args"] {
if let mapping = value as? [String: Any] {
for key in mapping.keys.sorted() {
let value = mapping[key]!
if value is NSNull {
if let inherited = vars[key] { buildArguments.append("\(key)=\(inherited)") }
} else if value is String || value is NSNumber {
buildArguments.append("\(key)=\(value)")
} else { throw ComposeError.message("Build argument \(key) for \(key) must be a scalar") }
}
} else if let list = value as? [String] {
for item in list {
if item.contains("=") { buildArguments.append(item) }
else if let inherited = vars[item] { buildArguments.append("\(item)=\(inherited)") }
}
} else { throw ComposeError.message("Service \(key) build.args must be a mapping or string list") }
}
let image = (raw["image"] as? String) ?? "\(resolvedProjectName)-\(key):latest"
guard context != nil || raw["image"] is String else { throw ComposeError.message("Service \(key) requires image or build") }
let deps: [String]
@@ -142,7 +218,7 @@ public struct ComposeProject {
if let arr = raw["command"] as? [String] { command = arr }
else if let str = raw["command"] as? String { command = ["sh", "-c", str] }
else { command = [] }
return ComposeService(id: key, image: image, buildContext: context, dockerfile: buildMap?["dockerfile"] as? String, ports: Self.strings(raw["ports"]), volumes: Self.strings(raw["volumes"]), environment: env, dependencies: deps, networks: networks, command: command, workingDirectory: raw["working_dir"] as? String, restart: raw["restart"] as? String)
return ComposeService(id: key, image: image, buildContext: context, dockerfile: buildMap?["dockerfile"] as? String, buildArguments: buildArguments, ports: Self.strings(raw["ports"]), volumes: Self.strings(raw["volumes"]), environment: env, dependencies: deps, networks: networks, command: command, workingDirectory: raw["working_dir"] as? String, restart: raw["restart"] as? String)
}
let ids = Set(services.map(\.id))
for service in services { for dep in service.dependencies where !ids.contains(dep) { throw ComposeError.message("Unknown dependency \(dep) for service \(service.id)") } }
@@ -205,6 +281,7 @@ public struct ComposeEngine {
private func build(_ service: ComposeService) throws {
guard let context = service.buildContext else { return }
var args = ["build", "-t", service.image]
for argument in service.buildArguments { args += ["--build-arg", argument] }
if let dockerfile = service.dockerfile { args += ["-f", path((context as NSString).appendingPathComponent(dockerfile))] }
args.append(path(context))
print(try run(args))
@@ -212,7 +289,7 @@ public struct ComposeEngine {
public func execute(_ action: String, args: [String] = [], attached: Bool = false, verbosity: Int = 0) throws -> String {
switch action {
case "doctor":
var lines = ["Apple Container Compose diagnostics", "Project: \(project.file.path)"]
var lines = ["Container Compose diagnostics", "Project: \(project.file.path)"]
guard let binary = Shell.executable("container") else { return (lines + ["FAIL: Apple container CLI not found. Install from https://github.com/apple/container/releases"]).joined(separator: "\n") }
lines.append("CLI: \(binary)")
let version = try Shell.run("container", ["--version"]); lines.append(version.output.trimmingCharacters(in: .whitespacesAndNewlines))
@@ -243,7 +320,7 @@ public struct ComposeEngine {
}
let rebuild = args.contains("--build")
if verbosity > 0 {
fputs("accompose: project=\(project.name) mode=\(attached ? "foreground" : "detached") verbosity=\(verbosity)\n", stderr)
fputs("container-compose: project=\(project.name) mode=\(attached ? "foreground" : "detached") verbosity=\(verbosity)\n", stderr)
}
if verbosity > 1 { fputs("Compose file: \(project.file.path)\n", stderr) }
if verbosity > 2 { fputs("Startup order: \(try project.orderedServices().map(\.id).joined(separator: ", "))\n", stderr) }
@@ -323,7 +400,7 @@ public struct ComposeEngine {
let all = args.contains("--all") || args.contains("--project")
let selected = args.first { !$0.hasPrefix("-") }
let targets = all ? project.services : project.services.filter { $0.id == selected || project.containerName($0) == selected }
guard !targets.isEmpty else { throw ComposeError.message("Select a service/container or use --all; see accompose clean --help") }
guard !targets.isEmpty else { throw ComposeError.message("Select a service/container or use --all; see container-compose clean --help") }
var output = ""
for service in targets {
let name = project.containerName(service)
@@ -336,7 +413,7 @@ public struct ComposeEngine {
return output
case "prune":
// Only safe, explicit image cleanup; no blind deletion of user data.
guard args.contains("--images") else { return "No resources deleted. Use accompose prune --images to request unused image pruning; named volumes are never pruned automatically.\n" }
guard args.contains("--images") else { return "No resources deleted. Use container-compose prune --images to request unused image pruning; named volumes are never pruned automatically.\n" }
if args.contains("--dry-run") { return "Dry run: inspect unused images with container image list; no changes made.\n" }
let result = try Shell.run("container", ["image", "prune"])
guard result.status == 0 else { throw ComposeError.message("Image prune unsupported or failed: \(result.output)") }
@@ -344,12 +421,16 @@ public struct ComposeEngine {
case "ps": return try run(["list", "--all"])
case "logs":
let selected = args.filter { !$0.hasPrefix("-") }
let unknown = Set(selected).subtracting(project.services.map(\.id))
guard unknown.isEmpty else { throw ComposeError.message("Unknown services: \(unknown.sorted().joined(separator: ", "))") }
let services = selected.isEmpty ? project.services : project.services.filter { selected.contains($0.id) }
var output = ""
for service in services { output += "=== \(service.id) ===\n" + (try run(["logs", project.containerName(service)])) }
return output
case "start", "stop", "restart":
let selected = args.filter { !$0.hasPrefix("-") }
let unknown = Set(selected).subtracting(project.services.map(\.id))
guard unknown.isEmpty else { throw ComposeError.message("Unknown services: \(unknown.sorted().joined(separator: ", "))") }
let services = selected.isEmpty ? project.services : project.services.filter { selected.contains($0.id) }
var output = ""
for service in services {
@@ -359,8 +440,15 @@ public struct ComposeEngine {
}
return output
case "exec":
guard let service = args.first, let match = project.services.first(where: { $0.id == service }), args.count > 1 else { throw ComposeError.message("Usage: accompose exec SERVICE COMMAND [ARGS]") }
return try run(["exec", project.containerName(match)] + Array(args.dropFirst()))
guard let service = args.first, let match = project.services.first(where: { $0.id == service }), args.count > 1 else { throw ComposeError.message("Usage: container-compose exec SERVICE COMMAND [ARGS]") }
guard let binary = Shell.executable("container") else { throw ComposeError.message("Missing container executable") }
let process = Process(); process.executableURL = URL(fileURLWithPath: binary)
process.currentDirectoryURL = project.directory
process.arguments = ["exec"] + (isatty(STDIN_FILENO) != 0 ? ["-i", "-t"] : ["-i"]) + [project.containerName(match)] + Array(args.dropFirst())
process.standardInput = FileHandle.standardInput; process.standardOutput = FileHandle.standardOutput; process.standardError = FileHandle.standardError
try process.run(); process.waitUntilExit()
guard process.terminationStatus == 0 else { throw ComposeError.message("exec failed (\(process.terminationStatus))") }
return ""
default: throw ComposeError.message("Unsupported command: \(action)")
}
}
+4 -1
View File
@@ -2,6 +2,7 @@ import SwiftUI
import AppKit
@MainActor final class ComposeModel: ObservableObject {
@Published var projectName = ""
@Published var projectURL: URL?
@Published var services: [ComposeService] = []
@Published var selectedService: String?
@@ -17,6 +18,7 @@ import AppKit
func load(_ url: URL) {
do {
let project = try ComposeProject(file: url)
projectName = project.name
projectURL = url; services = project.services
selectedService = services.first?.id
output += "Loaded \(url.path) (\(services.count) services)\n"
@@ -33,7 +35,8 @@ import AppKit
}
private func run(url: URL, action: String) {
guard !busy else { return }
busy = true; status = "Running \(action)…"; output += "\n$ acccompose \(action)\n"
guard !CommandLine.arguments.contains("--demo") else { output += "Demo mode is read-only. Relaunch normally to run Compose actions.\n"; return }
busy = true; status = "Running \(action)…"; output += "\n$ container-compose \(action)\n"
Task.detached {
let result: String
do { result = try ComposeEngine(ComposeProject(file: url)).execute(action) }
+1 -1
View File
@@ -27,7 +27,7 @@ struct ContentView: View {
VStack(alignment: .leading, spacing: 14) {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text("Apple Container Compose").font(.title2.bold())
Text("Container Compose").font(.title2.bold())
Text(model.projectURL?.deletingLastPathComponent().path ?? "Open a Compose project to get started")
.font(.caption).foregroundStyle(.secondary).textSelection(.enabled)
}
+131
View File
@@ -0,0 +1,131 @@
import Foundation
struct MonitorCLI {
static func run(_ arguments: [String]) async throws {
var args = arguments, binary = ContainerService.defaultBinary, demo = false, project = ""
while let first = args.first, first.hasPrefix("--") {
args.removeFirst()
switch first {
case "--help": print(help); return
case "--version": print("container-compose monitor Swift 1.1.0"); return
case "--demo": demo = true
case "--container", "--project": guard !args.isEmpty else { throw ServiceError.message("\(first) needs a value") }; let value = args.removeFirst(); if first == "--container" { binary = value } else { project = value }
default: throw ServiceError.message("Unknown option \(first). Use --help.")
}
}
let command = args.isEmpty ? "help" : args.removeFirst()
let service = ContainerService(binary: binary, demo: demo)
var native: [String]
switch command {
case "help": print(help); return
case "app":
let executable = URL(fileURLWithPath: CommandLine.arguments[0]).standardizedFileURL
let adjacent = executable.deletingLastPathComponent().deletingLastPathComponent().appendingPathComponent("Container Compose.app")
let embedded = executable.deletingLastPathComponent().deletingLastPathComponent().deletingLastPathComponent()
let target = embedded.pathExtension == "app" ? embedded.path : FileManager.default.fileExists(atPath: adjacent.path) ? adjacent.path : "Container Compose"
let p = Process(); p.executableURL = URL(fileURLWithPath: "/usr/bin/open"); p.arguments = ["-a", target] + (demo ? ["--args", "--demo"] : []); try p.run(); p.waitUntilExit(); if p.terminationStatus != 0 { throw ServiceError.message("Install Container Compose.app in Applications, or launch it directly.") }; return
case "watch", "monitor": try await watch(args, service: service); return
case "volume", "network", "image":
guard let verb = args.first, ["list", "inspect", "create", "delete", "prune", "pull"].contains(verb) else { throw ServiceError.message("Use container-compose monitor \(command) list|inspect|create|delete|prune [native arguments]") }
if command == "volume", verb == "list", args.contains("--all-mounts") { try await printVolumeInventory(service); return }
if verb == "prune" { guard args.contains("--yes") else { throw ServiceError.message("Resource pruning requires --yes") }; args.removeAll { $0 == "--yes" } }
if verb == "list", args.count == 1 { args += ["--format", "json"] }
native = [command] + args
case "ps": native = ["list", "--all", "--format", "json"] + args
case "volumes" where args.contains("--all-mounts"): try await printVolumeInventory(service); return
case "images", "networks", "volumes": native = [String(command.dropLast()), "list", "--format", "json"] + args
case "projects":
let resources = try await service.list(.projects, project: project).filter { !$0.project.isEmpty && (project.isEmpty || $0.project == project) }
let rows = resources.map { ["id": $0.id, "project": $0.project, "service": $0.service, "status": $0.state] }; print(ContainerService.pretty(rows)); return
case "logs", "inspect", "start", "stop", "delete", "kill": guard !args.isEmpty else { throw ServiceError.message("\(command) needs a target") }; native = [command] + args
case "stats" where args.contains("--watch"): args.removeAll { $0 == "--watch" }; try await watch(args, service: service); return
case "stats": native = ["stats", "--no-stream", "--format", "json"] + args
case "restart":
guard args.count == 1, let id = args.first, !id.isEmpty, !id.hasPrefix("-") else { throw ServiceError.message("restart requires exactly one container ID") }
let resources = try await service.list(.containers)
guard let resource = resources.first(where: { $0.id == id }) else { throw ServiceError.message("Container not found: \(id)") }
print(try await service.action(kind: .containers, action: "restart", resource: resource)); return
case "prune":
guard args.contains("--yes") else { throw ServiceError.message("Prune requires --yes. It affects ALL unused resources of the chosen type, regardless of --project.") }
let kinds = args.filter { $0 != "--yes" }; guard kinds.count <= 1, let kind = ResourceKind(rawValue: kinds.first ?? "containers"), kind != .projects, kind != .monitor else { throw ServiceError.message("Usage: container-compose monitor prune [containers|images|networks|volumes] --yes") }; native = ContainerService.actionArguments(kind: kind, action: "prune", id: "")
default: throw ServiceError.message("Unknown command: \(command). Use --help.")
}
if command == "logs", !demo {
let p = Process(); p.executableURL = URL(fileURLWithPath: binary); p.arguments = native; p.standardInput = FileHandle.standardInput; p.standardOutput = FileHandle.standardOutput; p.standardError = FileHandle.standardError
try p.run(); p.waitUntilExit(); if p.terminationStatus != 0 { exit(p.terminationStatus) }; return
}
print(try await service.run(native))
}
static func printVolumeInventory(_ service: ContainerService) async throws {
let volumes = try await service.list(.volumes), containers = try await service.list(.containers)
let rows = VolumeInventory.combine(volumes: volumes, containers: containers).map { r in ["id": r.id, "name": r.name, "type": r.storageKind, "capacity": r.capacity, "driver": r.driver, "format": r.format, "source": r.source, "created": r.created, "usedBy": r.usedBy, "inspectOnly": r.readOnlyStorage ? "true" : "false"] }
print(ContainerService.pretty(rows))
}
static func watch(_ arguments: [String], service: ContainerService) async throws {
var args = arguments, interval = 1.0, count: Int?, json = false, ids: [String] = []
while !args.isEmpty {
let option = args.removeFirst()
switch option {
case "--interval": guard !args.isEmpty, let value = Double(args.removeFirst()), value >= 0.25, value.isFinite else { throw ServiceError.message("--interval needs a finite number >= 0.25 seconds") }; interval = value
case "--count": guard !args.isEmpty, let value = Int(args.removeFirst()), value > 0 else { throw ServiceError.message("--count needs a positive number") }; count = value
case "--json": json = true
default: guard !option.hasPrefix("-") else { throw ServiceError.message("Unknown watch option: \(option)") }; ids.append(option)
}
}
var tracker = NetworkRateTracker(), history: [Double] = [], samples = 0
while !Task.isCancelled {
let start = Date()
var rows = try await service.list(.containers)
if !ids.isEmpty { rows = rows.filter { ids.contains($0.id) }; guard rows.count == Set(ids).count else { throw ServiceError.message("One or more requested containers were not found") } }
rows = try await service.withStats(rows)
let time = Date(), rates = tracker.sample(rows, at: time), cpu = rows.reduce(0) { $0 + ($1.cpuFraction ?? 0) * 100 }
history.append(cpu); history = Array(history.suffix(50)); samples += 1
if json {
let resourceRows: [[String: Any]] = rows.map { ["id": $0.id, "state": $0.state, "cpuPercent": $0.cpuFraction.map { $0 * 100 } as Any? ?? NSNull(), "memoryBytes": $0.memoryBytes as Any? ?? NSNull(), "networkRxBytes": $0.rxBytes as Any? ?? NSNull(), "networkTxBytes": $0.txBytes as Any? ?? NSNull()] }
let value: [String: Any] = ["timestamp": ISO8601DateFormatter().string(from: time), "containers": resourceRows, "networkRxBytesPerSecond": rates.rx, "networkTxBytesPerSecond": rates.tx, "demo": service.demo]
let data = try JSONSerialization.data(withJSONObject: value, options: [.sortedKeys]); print(String(decoding: data, as: UTF8.self))
} else {
if isatty(STDOUT_FILENO) != 0 { print("\u{001B}[2J\u{001B}[H", terminator: "") }
print("container-compose monitor live monitor" + (service.demo ? " · DEMO READ-ONLY" : " · Apple Containers"))
print(time.formatted(date: .omitted, time: .standard) + " · " + String(rows.filter { $0.state == "running" }.count) + " running · CPU " + String(format: "%.2f%%",cpu))
print("Network ↓ " + ContainerService.bytes(rates.rx) + "/s ↑ " + ContainerService.bytes(rates.tx) + "/s")
let glyphs = Array("▁▂▃▄▅▆▇█"), peak = max(history.max() ?? 0, 1)
print("CPU " + history.map { String(glyphs[min(7, Int(max(0,$0) / peak * 7))]) }.joined())
print("NAME".padding(toLength: 32, withPad: " ", startingAt: 0) + "STATE CPU MEMORY NET RX / TX")
for r in rows { print(String(r.name.prefix(30)).padding(toLength: 32, withPad: " ", startingAt: 0) + r.state.padding(toLength: 12, withPad: " ", startingAt: 0) + r.cpu.padding(toLength: 13, withPad: " ", startingAt: 0) + r.memory.padding(toLength: 13, withPad: " ", startingAt: 0) + r.network) }
print("Ctrl+C to exit · native CPU samples take about 2s · no terminal input mode changes")
}
fflush(stdout)
if let count, samples >= count { return }
let remaining = interval - Date().timeIntervalSince(start)
if remaining > 0 { try await Task.sleep(for: .seconds(remaining)) }
}
}
static let help = """
container-compose monitor Swift 1.1.0 — Apple Container command-line companion
Usage: container-compose monitor [--container PATH] [--demo] [--project NAME] COMMAND [ARGS]
app Open the native macOS app
watch [--interval S] [--count N] [--json] [ID...]
Live terminal table + CPU sparkline; --json emits NDJSON
monitor Alias for watch
ps All containers as JSON
projects Recognized Compose members as JSON
volume list [--all-mounts] Named volumes; --all-mounts includes host mounts/references
volume inspect NAME... Volume details
volume create [-s SIZE] NAME
volume delete NAME... Delete named volumes
volume prune --yes Prune unreferenced named volumes
network|image SUBCOMMAND Native resource commands
images | networks | volumes Resource list as JSON
logs [-n N] [--follow] ID Logs; native streaming has no timeout
inspect ID... Container configuration as JSON
stats [ID...] JSON snapshot; --watch enables live monitoring
start ID... | stop ID... | delete ID... | kill ID...
restart ID Start if stopped, otherwise stop then start
prune [containers|images|networks|volumes] --yes
help | --help | --version
Global options precede COMMAND. Native options follow it.
Commands use Apple's container CLI; Docker is not required.
Non-streaming commands time out after 30s; captured output is capped at 4 MiB.
"""
}
+154
View File
@@ -0,0 +1,154 @@
import Foundation
public enum ResourceKind: String, CaseIterable, Identifiable, Sendable {
case monitor, containers, images, networks, volumes, projects
public var id: String { rawValue }
public var title: String { rawValue.capitalized }
public var symbol: String { switch self { case .monitor: "waveform.path.ecg"; case .containers: "shippingbox"; case .images: "square.stack.3d.up"; case .networks: "network"; case .volumes: "externaldrive"; case .projects: "folder" } }
public var isContainer: Bool { self == .containers || self == .projects || self == .monitor }
public var prefix: [String] { isContainer ? [] : [String(rawValue.dropLast())] }
public var listArguments: [String] { isContainer ? ["list", "--all", "--format", "json"] : prefix + ["list", "--format", "json"] }
}
public struct Resource: Identifiable, Sendable, Hashable {
public var id: String
public var name: String
public var state: String
public var image: String
public var project: String
public var service: String
public var ports: String
public var raw: String
public var driver = "—"
public var format = "—"
public var source = "—"
public var created = "—"
public var capacity = "—"
public var storageKind = "Named volume"
public var usedBy = "—"
public var readOnlyStorage = false
public var cpuFraction: Double?
public var memoryBytes: Double?
public var rxBytes: Double?
public var txBytes: Double?
public var cpu = "—"
public var memory = "—"
public var network = "—"
public var disk = "—"
}
public enum ServiceError: LocalizedError {
case message(String)
public var errorDescription: String? { if case .message(let s) = self { return s }; return nil }
}
private final class Capture: @unchecked Sendable {
private let lock = NSLock()
private var data = Data()
func append(_ bytes: Data) { lock.lock(); defer { lock.unlock() }; let remaining = 4 * 1024 * 1024 - data.count; if remaining > 0 { data.append(bytes.prefix(remaining)) } }
var text: String { lock.lock(); defer { lock.unlock() }; return String(decoding: data, as: UTF8.self) }
}
public struct ContainerService: Sendable {
public var binary: String
public var demo: Bool
public init(binary: String = ContainerService.defaultBinary, demo: Bool = false) { self.binary = binary; self.demo = demo }
public static var defaultBinary: String {
let paths = (ProcessInfo.processInfo.environment["PATH"] ?? "").split(separator: ":").map(String.init) + ["/opt/homebrew/bin", "/usr/local/bin", "/usr/bin"]
return paths.map { $0 + "/container" }.first { FileManager.default.isExecutableFile(atPath: $0) } ?? "/opt/homebrew/bin/container"
}
public func run(_ args: [String], timeout: TimeInterval = 30) async throws -> String {
try await Task.detached { try execute(args, timeout: timeout) }.value
}
public func execute(_ args: [String], timeout: TimeInterval = 30) throws -> String {
if demo {
let verb = ["image", "network", "volume"].contains(args.first ?? "") ? args.dropFirst().first ?? "" : args.first ?? ""
guard ["list", "inspect", "logs", "stats", "exec"].contains(verb) else { throw ServiceError.message("Demo mode is read-only. Connect to the live runtime to control containers.") }
return try demoOutput(args)
}
guard FileManager.default.isExecutableFile(atPath: binary) else { throw ServiceError.message("Apple container CLI was not found at \(binary). Install it or choose its executable in Settings.") }
let process = Process(); process.executableURL = URL(fileURLWithPath: binary); process.arguments = args
let out = Pipe(), err = Pipe(); process.standardOutput = out; process.standardError = err
let stdout = Capture(), stderr = Capture(); let group = DispatchGroup()
let ended = DispatchSemaphore(value: 0); process.terminationHandler = { _ in ended.signal() }
try process.run()
for (handle, capture) in [(out.fileHandleForReading, stdout), (err.fileHandleForReading, stderr)] {
group.enter(); DispatchQueue.global().async { defer { group.leave() }; while true { let data = handle.availableData; if data.isEmpty { break }; capture.append(data) } }
}
let timedOut = ended.wait(timeout: .now() + timeout) == .timedOut
if timedOut { process.terminate(); if ended.wait(timeout: .now() + 2) == .timedOut { kill(process.processIdentifier, SIGKILL); process.waitUntilExit() } }
group.wait()
if timedOut { throw ServiceError.message("container \(args.joined(separator: " ")) timed out.") }
guard process.terminationStatus == 0 else { throw ServiceError.message("container \(args.joined(separator: " ")) failed:\n\(stderr.text)") }
return stdout.text
}
public static func parse(_ data: String, kind: ResourceKind, projectHint: String = "") throws -> [Resource] {
guard let bytes = data.data(using: .utf8), let rows = try JSONSerialization.jsonObject(with: bytes) as? [[String: Any]] else { throw ServiceError.message("Unexpected container JSON: expected a resource array.") }
return try rows.map { row in
let cfg = row["configuration"] as? [String: Any] ?? row
let status = row["status"] as? [String: Any] ?? [:]
let image = cfg["image"] as? [String: Any] ?? [:]
let id = kind.isContainer ? (cfg["id"] ?? row["id"]) : (cfg["name"] ?? row["reference"] ?? row["name"] ?? cfg["id"] ?? row["id"])
guard let id = id as? String, !id.isEmpty else { throw ServiceError.message("Unsupported \(kind.title) JSON: missing identifier.") }
let labels = cfg["labels"] as? [String: String] ?? [:]
var project = "", service = ""
for prefix in ["com.docker.compose.", "io.container-compose.", "com.container-compose.", "container-compose.", "io.accompose.", "com.accompose.", "accompose."] { if let p = labels[prefix + "project"] { project = p; service = labels[prefix + "service"] ?? ""; break } }
if project.isEmpty, !projectHint.isEmpty, id.hasPrefix(projectHint + "-"), id.hasSuffix("-1") { project = projectHint; service = String(id.dropFirst(projectHint.count + 1).dropLast(2)) }
var resource = Resource(id: id, name: id, state: status["state"] as? String ?? row["status"] as? String ?? "—", image: image["reference"] as? String ?? image["name"] as? String ?? (cfg["descriptor"] as? [String: Any])?["digest"] as? String ?? "—", project: project, service: service, ports: pretty(cfg["publishedPorts"] ?? []), raw: pretty(row))
if kind == .volumes {
resource.driver = cfg["driver"] as? String ?? "—"
resource.format = cfg["format"] as? String ?? "—"
resource.source = cfg["source"] as? String ?? "—"
resource.created = cfg["creationDate"] as? String ?? cfg["createdAt"] as? String ?? "—"
resource.capacity = Self.bytes(cfg["sizeInBytes"])
resource.storageKind = labels["com.apple.container.resource.anonymous"] == nil ? "Named volume" : "Anonymous volume"
}
return resource
}.sorted { $0.name < $1.name }
}
public func list(_ kind: ResourceKind, project: String = "") async throws -> [Resource] { try Self.parse(await run(kind.listArguments), kind: kind, projectHint: project) }
public func withStats(_ resources: [Resource]) async throws -> [Resource] {
guard resources.contains(where: { $0.state == "running" }) else { return resources }
let output = try await run(["stats", "--no-stream", "--format", "json"])
guard let rows = try JSONSerialization.jsonObject(with: Data(output.utf8)) as? [[String: Any]] else { throw ServiceError.message("Unsupported stats JSON") }
return resources.map { resource in var r = resource; if let s = rows.first(where: { $0["id"] as? String == r.id }) { if let cpu = s["cpuUtilization"] as? Double { r.cpuFraction = cpu; r.cpu = String(format: "%.2f%%", cpu * 100) }; r.memoryBytes = s["memoryUsageBytes"] as? Double; r.rxBytes = s["networkRxBytes"] as? Double; r.txBytes = s["networkTxBytes"] as? Double; r.disk = Self.bytes(s["blockReadBytes"]) + " / " + Self.bytes(s["blockWriteBytes"]); r.memory = Self.bytes(s["memoryUsageBytes"]); r.network = Self.bytes(s["networkRxBytes"]) + " / " + Self.bytes(s["networkTxBytes"]) }; return r }
}
public static func bytes(_ value: Any?) -> String { guard let n = value as? NSNumber else { return "—" }; return ByteCountFormatter.string(fromByteCount: n.int64Value, countStyle: .binary) }
public static func detailArguments(kind: ResourceKind, id: String, mode: String) -> [String] {
if kind.isContainer { switch mode { case "Logs": return ["logs", "-n", "200", id]; case "Stats": return ["stats", "--no-stream", "--format", "json", id]; case "Processes": return ["exec", id, "ps", "aux"]; default: break } }
return kind.prefix + ["inspect", id]
}
public static func actionArguments(kind: ResourceKind, action: String, id: String) -> [String] { kind.prefix + [action] + (action == "prune" ? [] : [id]) }
public func waitForState(id: String, expected: String, timeout: TimeInterval = 15) async throws -> Resource {
let deadline = Date().addingTimeInterval(timeout)
while true {
let output = try await run(["inspect", id], timeout: min(5, timeout))
if let resource = try Self.parse(output, kind: .containers).first(where: { $0.id == id }), resource.state == expected { return resource }
guard Date() < deadline else { throw ServiceError.message("The command returned, but \(id) did not reach \(expected) within \(Int(timeout)) seconds. Check Inspect for its current state.") }
try await Task.sleep(for: .milliseconds(250))
}
}
public func action(kind: ResourceKind, action: String, resource: Resource?) async throws -> String {
guard !demo else { throw ServiceError.message("Demo mode is read-only. Connect to the live runtime to control containers.") }
guard resource?.readOnlyStorage != true else { throw ServiceError.message("Host mounts are inspect-only and cannot be deleted as volumes.") }
if action == "restart" { guard let r = resource else { throw ServiceError.message("Select a container") }; if r.state == "running" { _ = try await run(["stop", r.id]) }; return try await run(["start", r.id]) }
guard action == "prune" || resource != nil else { throw ServiceError.message("Select a resource") }
return try await run(Self.actionArguments(kind: kind, action: action, id: resource?.id ?? ""))
}
public static func pretty(_ value: Any) -> String { guard JSONSerialization.isValidJSONObject(value), let data = try? JSONSerialization.data(withJSONObject: value, options: [.prettyPrinted, .sortedKeys]), let s = String(data: data, encoding: .utf8) else { return String(describing: value) }; return s }
public static func formatted(_ string: String) -> String { guard let data = string.data(using: .utf8), let value = try? JSONSerialization.jsonObject(with: data) else { return string }; return pretty(value) }
private func demoOutput(_ args: [String]) throws -> String {
if args.first == "list" { return Self.demoContainers }
if args.prefix(2) == ["image", "list"] { return "[{\"configuration\":{\"name\":\"nginx:alpine\"}}]" }
if args.prefix(2) == ["network", "list"] { return "[{\"id\":\"default\",\"configuration\":{\"name\":\"default\"}}]" }
if args.prefix(2) == ["volume", "list"] { return "[{\"configuration\":{\"name\":\"demo-data\",\"driver\":\"local\",\"format\":\"ext4\",\"source\":\"/demo/demo-data.img\",\"creationDate\":\"2026-11-08T12:00:00Z\",\"sizeInBytes\":1073741824}}]" }
if args.first == "stats" { return Self.demoStats(at: Date()) }
if args.first == "logs" { return "12:00:01 Server ready on :80\n12:00:02 GET /health 200\nDemo mode · no runtime commands executed" }
if args.first == "inspect" { return Self.demoContainers }
return "Demo: \(args.joined(separator: " ")) (simulated; fixture state unchanged)"
}
public static func demoStats(at date: Date) -> String {
let t = date.timeIntervalSince1970, wave = sin(t / 13)
let row: [String: Any] = ["id": "demo-web-1", "cpuUtilization": 0.08 + 0.05 * wave, "memoryUsageBytes": (42 + 4 * wave) * 1_048_576, "networkRxBytes": t * 12 * 1024 - 8 * 1024 * 13 * cos(t / 13), "networkTxBytes": t * 7 * 1024 - 4 * 1024 * 13 * cos(t / 13)]
return pretty([row])
}
public static let demoContainers = """
[{"configuration":{"id":"demo-web-1","image":{"reference":"nginx:alpine"},"publishedPorts":[{"hostPort":8080,"containerPort":80}],"labels":{"io.container-compose.project":"demo","io.container-compose.service":"web"}},"status":{"state":"running"}},{"configuration":{"id":"demo-db-1","image":{"reference":"postgres:17"},"labels":{"io.container-compose.project":"demo","io.container-compose.service":"db"}},"status":{"state":"stopped"}}]
"""
}
@@ -0,0 +1,22 @@
import Foundation
/// Rates are computed per container so new or removed containers do not cause
/// spikes from their lifetime counters. Counter resets contribute zero.
public struct NetworkRateTracker: Sendable {
private struct Counter: Sendable { var date: Date; var rx: Double; var tx: Double }
private var counters: [String: Counter] = [:]
public init() {}
public mutating func sample(_ resources: [Resource], at date: Date) -> (rx: Double, tx: Double) {
var next: [String: Counter] = [:], rxRate = 0.0, txRate = 0.0
for resource in resources {
guard let rx = resource.rxBytes, let tx = resource.txBytes else { continue }
if let previous = counters[resource.id] {
let seconds = date.timeIntervalSince(previous.date)
if seconds > 0 { rxRate += max(0, rx - previous.rx) / seconds; txRate += max(0, tx - previous.tx) / seconds }
}
next[resource.id] = Counter(date: date, rx: rx, tx: tx)
}
counters = next
return (rxRate, txRate)
}
}
+35
View File
@@ -0,0 +1,35 @@
import Foundation
public enum VolumeInventory {
/// Managed volumes are enriched with configured references. Persistent host
/// mounts are displayed separately and can never be deleted as volumes.
public static func combine(volumes: [Resource], containers: [Resource], includeHostMounts: Bool = true) -> [Resource] {
var result = volumes
var uses: [String: Set<String>] = [:]
var host: [String: Resource] = [:]
for container in containers {
guard let data = container.raw.data(using: .utf8), let row = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let cfg = row["configuration"] as? [String: Any], let mounts = cfg["mounts"] as? [[String: Any]] else { continue }
for mount in mounts {
let type = mount["type"] as? [String: Any] ?? [:]
let source = mount["source"] as? String ?? ""
let destination = mount["destination"] as? String ?? ""
let reference = "\(container.name):\(destination)"
if let volume = type["volume"] as? [String: Any], let name = volume["name"] as? String { uses[name, default: []].insert(reference); continue }
if let volume = volumes.first(where: { $0.source == source && !source.isEmpty }) { uses[volume.id, default: []].insert(reference); continue }
guard includeHostMounts, !source.isEmpty, type["virtiofs"] != nil || type["block"] != nil else { continue }
let id = "host:" + source
var item = host[id] ?? Resource(id: id, name: URL(fileURLWithPath: source).lastPathComponent, state: "Referenced", image: "—", project: "", service: "", ports: "—", raw: ContainerService.pretty(mount))
item.source = source; item.storageKind = type["block"] == nil ? "Bind mount" : "Disk mount"; item.readOnlyStorage = true
item.driver = type["block"] == nil ? "virtiofs" : "block"; item.format = "host"; item.usedBy = ""
host[id] = item; uses[id, default: []].insert(reference)
}
}
result += host.values
for index in result.indices {
let references = uses[result[index].id] ?? []
result[index].usedBy = references.isEmpty ? "No container references" : references.sorted().joined(separator: ", ")
result[index].state = references.isEmpty ? "Unreferenced" : "Referenced"
}
return result.sorted { ($0.readOnlyStorage ? 1 : 0, $0.name) < ($1.readOnlyStorage ? 1 : 0, $1.name) }
}
}
+343
View File
@@ -0,0 +1,343 @@
import SwiftUI
import AppKit
import Charts
@MainActor final class Monitor: ObservableObject {
@Published var kind: ResourceKind = .monitor
@Published var resources: [Resource] = []
@Published var containerResources: [Resource] = []
@Published var lastSample: Date?
@Published var telemetryError = ""
@Published var sampling = false
@Published var history: [TelemetryPoint] = []
@Published var lastAction = ""
private var rateTracker = NetworkRateTracker()
@Published var telemetryAvailable = false
private var telemetrySource = ""
@Published var selection: String?
@Published var search = ""
@Published var mode = "Inspect"
@Published var detail = "Select a resource to inspect."
@Published var status = "Connecting…"
@Published var error: String?
@Published var busy = false
@Published var acting = false
@Published var showStopped = true
@Published var showHostMounts = true
@Published var showingConfirmation = false
@Published var pendingAction: String?
@Published var pendingResource: Resource?
@Published var pendingKind: ResourceKind = .containers
@Published var follow = true
@AppStorage("containerBinary") var binary = ContainerService.defaultBinary
@AppStorage("composeProject") var project = ""
@Published var demo = CommandLine.arguments.contains("--demo")
private var detailToken = UUID()
private var refreshToken = UUID()
private var mutationEpoch = 0
var service: ContainerService { ContainerService(binary: binary, demo: demo) }
var selected: Resource? { kind == .monitor ? nil : visible.first { $0.id == selection } }
var visible: [Resource] {
resources.filter { r in
(kind != .projects || (!r.project.isEmpty && (project.isEmpty || r.project == project))) &&
(showStopped || !kind.isContainer || r.state == "running") &&
(kind != .volumes || showHostMounts || !r.readOnlyStorage) &&
(search.isEmpty || (r.name + " " + r.image + " " + r.project + " " + r.source + " " + r.usedBy).localizedCaseInsensitiveContains(search))
}
}
func recordTelemetry(_ rows: [Resource], source: String) {
if source != telemetrySource { history = []; rateTracker = NetworkRateTracker(); telemetrySource = source }
let measured = rows.filter { $0.cpuFraction != nil || $0.memoryBytes != nil }
telemetryAvailable = !measured.isEmpty
guard !measured.isEmpty else { rateTracker = NetworkRateTracker(); return }
let now = Date(), cpu = measured.reduce(0) { $0 + ($1.cpuFraction ?? 0) * 100 }, memory = measured.reduce(0) { $0 + ($1.memoryBytes ?? 0) }
let rates = rateTracker.sample(measured, at: now)
if demo && history.isEmpty {
// Clearly labelled illustrative history makes the demo immediately explorable.
history = (0..<60).map { n in let date = now.addingTimeInterval(Double(n - 60) * 3); let wave = sin(date.timeIntervalSince1970 / 13); return TelemetryPoint(date: date, cpu: 8 + 5 * wave, memory: (42 + 4 * wave) * 1_048_576, rx: (12 + 8 * wave) * 1024, tx: (7 + 4 * wave) * 1024) }
}
history.append(TelemetryPoint(date: now, cpu: cpu, memory: memory, rx: rates.rx, tx: rates.tx))
history = history.filter { now.timeIntervalSince($0.date) <= 360 }.suffix(360).map { $0 }
}
func changeKind() { resources = []; selection = nil; detail = "Select a resource to inspect."; detailToken = UUID(); refreshToken = UUID(); busy = false }
func resetConnection() { changeKind(); containerResources = []; history = []; lastSample = nil; telemetryAvailable = false; telemetryError = ""; status = "Connecting…"; rateTracker = NetworkRateTracker() }
func connectLive() { demo = false; resetConnection() }
var connectionID: String { "\(binary)|\(demo)|\(project)" }
func sampleTelemetry() async {
let backend = service, connection = connectionID, epoch = mutationEpoch
sampling = true
defer { sampling = false }
do {
let listed = try await backend.list(.containers, project: project)
let measured = try await backend.withStats(listed)
guard !Task.isCancelled, connectionID == connection, mutationEpoch == epoch else { return }
containerResources = measured; lastSample = Date(); telemetryError = ""
recordTelemetry(measured, source: connection)
if kind.isContainer && !acting {
resources = measured
if !visible.contains(where: { $0.id == selection }) { selection = visible.first?.id }
}
if mode == "Stats", let selected { detail = "\(selected.name)\nCPU: \(selected.cpu)\nMemory: \(selected.memory)\nNetwork RX / TX (cumulative): \(selected.network)\nDisk read / write (cumulative): \(selected.disk)\nSample: \(Date.now.formatted(date: .omitted, time: .standard))" }
} catch {
guard !Task.isCancelled, connectionID == connection, mutationEpoch == epoch else { return }
telemetryError = error.localizedDescription; telemetryAvailable = false
}
}
func refresh() async {
guard !busy, !acting else { return }; busy = true
let token = UUID(); refreshToken = token
let requestedKind = kind, backend = service
defer { if refreshToken == token { busy = false } }
do {
var rows = try await backend.list(requestedKind, project: project)
guard !Task.isCancelled, refreshToken == token, kind == requestedKind else { return }
if requestedKind.isContainer {
rows = rows.map { row in
var result = row
if row.state == "running", let metrics = containerResources.first(where: { $0.id == row.id && $0.state == "running" }) { result.cpu = metrics.cpu; result.memory = metrics.memory; result.network = metrics.network; result.disk = metrics.disk }
return result
}
}
if requestedKind == .volumes {
let containers = try await backend.list(.containers, project: project)
guard !Task.isCancelled, refreshToken == token else { return }
rows = VolumeInventory.combine(volumes: rows, containers: containers)
}
resources = rows
if !visible.contains(where: { $0.id == selection }) { selection = visible.first?.id }
status = "Connected · \(rows.count) resources · \(rows.filter { $0.state == "running" }.count) running"
} catch { if refreshToken == token, !Task.isCancelled { status = "Unavailable: \(error.localizedDescription)"; resources = []; selection = nil; detail = "Check the Apple Container service and executable in Settings." } }
}
func loadDetail() async {
guard let resource = selected else { detail = "Select a resource to inspect."; return }
if kind == .volumes, resource.readOnlyStorage { detail = "\(resource.storageKind) · inspect only\nSource: \(resource.source)\nUsed by: \(resource.usedBy)\n\n\(resource.raw)"; return }
let token = UUID(); detailToken = token
let requestedMode = mode, requestedKind = kind, backend = service
do {
let result = try await backend.run(ContainerService.detailArguments(kind: requestedKind, id: resource.id, mode: requestedMode))
guard detailToken == token, !Task.isCancelled else { return }; detail = ContainerService.formatted(result)
} catch { if detailToken == token, !Task.isCancelled { detail = "Unavailable for this runtime or resource:\n\(error.localizedDescription)" } }
}
func request(_ action: String) { guard !acting else { return }; guard action == "prune" || selected?.readOnlyStorage != true else { error = "Host mounts are inspect-only. Manage their files on the host; they are not Apple named volumes."; return }; guard !demo else { error = "Demo mode is read-only. Use Connect to live runtime to control real containers."; return }; pendingResource = selected; pendingKind = kind; pendingAction = action; showingConfirmation = true }
func performPending() {
guard let action = pendingAction, !acting else { return }
let resource = pendingResource, requestedKind = pendingKind, backend = service
pendingAction = nil; acting = true; mutationEpoch += 1; refreshToken = UUID(); busy = false
lastAction = "\(action.capitalized) in progress · \(resource?.name ?? requestedKind.title)"
Task {
do {
let result = try await backend.action(kind: requestedKind, action: action, resource: resource)
if let resource, ["start", "stop", "restart"].contains(action) {
let expected = action == "stop" ? "stopped" : "running"
let verified = try await backend.waitForState(id: resource.id, expected: expected)
if let index = resources.firstIndex(where: { $0.id == verified.id }) { resources[index] = verified }
lastAction = "\(resource.name) is \(expected) · verified by runtime"
} else { lastAction = "Completed \(action) · \(resource?.name ?? requestedKind.title)" }
detail = result; status = lastAction
} catch { self.error = error.localizedDescription; lastAction = "\(action.capitalized) failed · \(error.localizedDescription)" }
acting = false; await refresh(); if mode != "Stats" { await loadDetail() }
}
}
func exportCLI() {
guard let source = Bundle.main.url(forAuxiliaryExecutable: "container-compose") else { error = "Build the release app bundle to include the command-line tool."; return }
let panel = NSSavePanel(); panel.nameFieldStringValue = "container-compose"; panel.title = "Save the container-compose command-line tool"
guard panel.runModal() == .OK, let url = panel.url else { return }
do { if FileManager.default.fileExists(atPath: url.path) { try FileManager.default.removeItem(at: url) }; try FileManager.default.copyItem(at: source, to: url); try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: url.path) } catch { self.error = error.localizedDescription }
}
}
struct Workspace: View {
@ObservedObject var model: Monitor
@State private var sidebarSelection: ResourceKind? = .monitor
var body: some View {
NavigationSplitView {
VStack(alignment: .leading, spacing: 18) {
HStack(spacing: 10) { Image(systemName: "shippingbox.fill").font(.largeTitle).foregroundStyle(.orange); VStack(alignment: .leading) { Text("Container Compose").font(.title.bold()); Text("Apple Containers").foregroundStyle(.secondary).font(.caption) } }.padding(.horizontal)
List(ResourceKind.allCases, selection: $sidebarSelection) { kind in Label(kind.title, systemImage: kind.symbol).tag(kind) }
VStack(alignment: .leading, spacing: 8) { Label(model.demo ? "Demo workspace" : "Local runtime", systemImage: model.demo ? "play.rectangle" : "desktopcomputer"); Text(model.demo ? "Fixture data · actions are simulated" : "Apple container CLI").font(.caption).foregroundStyle(.secondary) }.padding()
}.padding(.top, 12).navigationSplitViewColumnWidth(min: 190, ideal: 210)
} detail: {
VStack(spacing: 0) {
if model.demo {
HStack { Label("Demo preview · container controls are disabled", systemImage: "eye"); Spacer(); Button("Connect to live runtime") { model.connectLive() } }.font(.callout).padding(12).background(Color.orange.opacity(0.12))
}
if !model.lastAction.isEmpty { HStack { if model.acting { ProgressView().controlSize(.small) }; Text(model.lastAction).textSelection(.enabled); Spacer() }.font(.caption).padding(10).background(Color.accentColor.opacity(0.08)) }
if model.kind == .monitor {
MonitorDashboard(model: model)
} else {
HStack { VStack(alignment: .leading, spacing: 4) { Text(model.kind.title).font(.title2.bold()); Text("\(model.visible.count) visible resources").font(.caption).foregroundStyle(.secondary) }; Spacer(); if model.busy { ProgressView().controlSize(.small) }; if model.kind.isContainer { Toggle("Show stopped", isOn: $model.showStopped).toggleStyle(.checkbox) }; if model.kind == .volumes { Toggle("Include host mounts", isOn: $model.showHostMounts).toggleStyle(.checkbox) } }.padding()
VSplitView {
resourceTable.frame(minHeight: 140)
VStack(alignment: .leading, spacing: 12) {
if let r = model.selected {
HStack { Circle().fill(r.state == "running" ? Color.green : Color.secondary).frame(width: 7, height: 7); Text(r.name).font(.headline).textSelection(.enabled); Spacer(); Text(r.state).foregroundStyle(.secondary) }
Text(model.kind == .volumes ? "\(r.storageKind) · \(r.source) · \(r.usedBy)" : "\(r.image)\(r.project.isEmpty ? "" : " · " + r.project + " / " + r.service)").font(.caption).foregroundStyle(.secondary).textSelection(.enabled)
}
HStack {
Picker("Details", selection: $model.mode) { Text("Inspect").tag("Inspect"); if model.kind.isContainer { Text("Logs").tag("Logs"); Text("Stats").tag("Stats"); Text("Processes").tag("Processes") } }.pickerStyle(.segmented).frame(maxWidth: 460)
Spacer(); if model.mode == "Logs" { Toggle("Follow", isOn: $model.follow).toggleStyle(.checkbox) }
Button { NSPasteboard.general.clearContents(); NSPasteboard.general.setString(model.detail, forType: .string) } label: { Image(systemName: "doc.on.doc") }.help("Copy details")
}
ScrollViewReader { proxy in
ScrollView([.horizontal, .vertical]) { VStack(alignment: .leading) { Text(model.detail).font(.system(.caption, design: .monospaced)).textSelection(.enabled).frame(maxWidth: .infinity, alignment: .leading); Color.clear.frame(height: 1).id("end") }.padding(12) }
.background(Color(nsColor: .textBackgroundColor), in: RoundedRectangle(cornerRadius: 8))
.onChange(of: model.detail) { _, _ in if model.follow && model.mode == "Logs" { proxy.scrollTo("end", anchor: .bottom) } }
}
}.padding().frame(minHeight: 240)
}
}
Divider()
HStack { Circle().fill(model.status.hasPrefix("Connected") ? .green : .orange).frame(width: 6, height: 6); Text(model.status).font(.caption).lineLimit(2).textSelection(.enabled); Spacer(); TimelineView(.periodic(from: .now, by: 1)) { context in
if let sample = model.lastSample { let age = context.date.timeIntervalSince(sample); Text("Sample \(Int(max(0,age)))s ago").foregroundStyle(age > 8 ? Color.orange : Color.secondary).font(.caption).monospacedDigit() }
}; Text(model.sampling ? "Sampling…" : "Swift Edition").font(.caption).foregroundStyle(.secondary) }.padding(10)
}.background(Color(nsColor: .windowBackgroundColor))
.searchable(text: $model.search, prompt: "Filter names, images, projects")
.toolbar {
ToolbarItemGroup {
Button { Task { await model.refresh() } } label: { Label("Refresh", systemImage: "arrow.clockwise") }.disabled(model.busy || model.acting)
if model.kind.isContainer && model.kind != .monitor { Button { model.request("start") } label: { Label("Start", systemImage: "play.fill") }.disabled(model.selected == nil || model.acting || model.demo); Button { model.request("stop") } label: { Label("Stop", systemImage: "stop.fill") }.disabled(model.selected == nil || model.acting || model.demo) }
Menu { if model.kind.isContainer { Button("Restart") { model.request("restart") }; Button("Kill…", role: .destructive) { model.request("kill") } }; Button("Delete selected…", role: .destructive) { model.request("delete") } } label: { Label("Actions", systemImage: "ellipsis.circle") }.disabled(model.selected == nil || model.selected?.readOnlyStorage == true || model.acting || model.demo)
Button { model.request("prune") } label: { Label("Prune…", systemImage: "trash") }.disabled(model.acting || model.demo || model.kind == .monitor)
}
}
}
.onChange(of: model.kind) { _, kind in if sidebarSelection != kind { sidebarSelection = kind } }
.onChange(of: sidebarSelection) { _, kind in if let kind { model.kind = kind; model.mode = "Inspect"; model.changeKind() } }
.onChange(of: model.binary) { _, _ in model.resetConnection() }
.onChange(of: model.demo) { _, _ in model.resetConnection() }
.onChange(of: model.project) { _, _ in model.resetConnection() }
.task(id: "\(model.kind.rawValue)|\(model.connectionID)") { while !Task.isCancelled { await model.refresh(); do { try await Task.sleep(for: .seconds(2)) } catch { break } } }
.task(id: model.connectionID) { while !Task.isCancelled { await model.sampleTelemetry(); do { try await Task.sleep(for: .milliseconds(model.demo ? 1000 : 100)) } catch { break } } }
.task(id: "\(model.selection ?? "")|\(model.mode)|\(model.connectionID)") {
if model.mode == "Stats" { return }
repeat { await model.loadDetail(); if model.mode == "Inspect" { break }; do { try await Task.sleep(for: .seconds(1)) } catch { break } } while !Task.isCancelled
}
.alert("Confirm \(model.pendingAction ?? "action")", isPresented: $model.showingConfirmation) {
Button("Cancel", role: .cancel) { model.pendingAction = nil }
Button("Confirm", role: .destructive) { model.performPending() }
} message: { Text(model.pendingAction == "prune" ? "This affects ALL unused \(model.pendingKind == .projects ? "containers" : model.pendingKind.rawValue), regardless of project or search filters. Container prune removes stopped containers; image prune removes dangling images." : "\(model.pendingAction?.capitalized ?? "Change") \(model.pendingResource?.name ?? "selected resource")?") }
.alert("Command failed", isPresented: Binding(get: { model.error != nil }, set: { if !$0 { model.error = nil } })) { Button("OK") { model.error = nil } } message: { Text(model.error ?? "") }
}
@ViewBuilder private var resourceTable: some View {
if model.kind == .volumes {
Table(model.visible, selection: $model.selection) {
TableColumn("Name", value: \.name).width(min: 140, ideal: 190)
TableColumn("Type", value: \.storageKind).width(110)
TableColumn("Capacity", value: \.capacity).width(90)
TableColumn("Driver", value: \.driver).width(70)
TableColumn("Format", value: \.format).width(65)
TableColumn("Used by", value: \.usedBy).width(min: 150, ideal: 220)
TableColumn("Source", value: \.source).width(min: 160, ideal: 250)
TableColumn("Created", value: \.created).width(min: 140, ideal: 180)
}.overlay { if model.visible.isEmpty { ContentUnavailableView("No named volumes or host mounts", systemImage: "externaldrive", description: Text("This runtime currently reports no volumes. Use container-compose monitor volume create NAME, or mount a host directory in a container. Bind mounts are shown separately and are inspect-only.")) } }
} else {
Table(model.visible, selection: $model.selection) {
TableColumn("Name", value: \.name).width(min: 150, ideal: 220)
TableColumn("State") { r in Text(r.state).foregroundStyle(r.state == "running" ? .green : .secondary) }.width(75)
TableColumn("Image / Digest", value: \.image).width(min: 130, ideal: 240)
TableColumn("CPU", value: \.cpu).width(65)
TableColumn("Memory", value: \.memory).width(85)
TableColumn("Network RX / TX", value: \.network).width(min: 110, ideal: 140)
TableColumn("Disk read / write", value: \.disk).width(min: 110, ideal: 140)
TableColumn("Project", value: \.project).width(min: 80, ideal: 120)
}.overlay { if model.visible.isEmpty { ContentUnavailableView(model.search.isEmpty ? "No \(model.kind.rawValue)" : "No matches", systemImage: model.kind.symbol, description: Text(model.kind == .projects ? "Set your container-compose project name in Settings, or use containers with Compose labels." : "Refresh the list or check the runtime status below.")) } }
}
}
}
struct Preferences: View {
@ObservedObject var model: Monitor
var body: some View {
Form {
TextField("Container executable", text: $model.binary)
TextField("container-compose project", text: $model.project)
Text("Recognizes <project>-<service>-1 names. Compose labels are also recognized. Actions operate on individual containers.").font(.caption).foregroundStyle(.secondary)
Toggle("Demo mode", isOn: $model.demo)
Text("Logs show the latest 200 lines and refresh every second. Telemetry runs continuously (about two seconds per native sample). Stats, volumes and processes depend on runtime support.").font(.caption).foregroundStyle(.secondary)
Button("Save Command-Line Tool…") { model.exportCLI() }
Link("Apple Container installation", destination: URL(string: "https://github.com/apple/container")!)
}.padding(24).frame(width: 560)
}
}
struct TelemetryPoint: Identifiable {
let id = UUID()
var date: Date
var cpu: Double
var memory: Double
var rx: Double
var tx: Double
}
struct MonitorDashboard: View {
@ObservedObject var model: Monitor
private let columns = [GridItem(.flexible()), GridItem(.flexible())]
private var current: TelemetryPoint? { model.telemetryAvailable ? model.history.last : nil }
private var running: Int { model.containerResources.filter { $0.state == "running" }.count }
var body: some View {
ScrollView {
VStack(alignment: .leading, spacing: 22) {
if !model.telemetryError.isEmpty { Label("Telemetry unavailable: " + model.telemetryError, systemImage: "exclamationmark.triangle").font(.callout).foregroundStyle(.orange).textSelection(.enabled) }
HStack(alignment: .top) {
VStack(alignment: .leading, spacing: 7) {
Text("Container Monitor").font(.system(size: 30, weight: .bold, design: .rounded))
Text(model.demo ? "DEMO · Illustrative history and fixture metrics" : "Live Apple Container telemetry · continuous two-second samples").font(.callout).foregroundStyle(.secondary)
}
Spacer()
Label(model.demo ? "Demo" : "Live", systemImage: "circle.fill").font(.caption.bold()).foregroundStyle(model.demo ? .orange : .mint).padding(.horizontal, 12).padding(.vertical, 7).background((model.demo ? Color.orange : Color.mint).opacity(0.12), in: Capsule())
}
HStack(spacing: 14) {
metricCard("Running", value: "\(running) / \(model.containerResources.count)", subtitle: "containers", symbol: "shippingbox.fill", color: .orange)
metricCard("CPU", value: current.map { String(format: "%.1f%%", $0.cpu) } ?? "—", subtitle: "of one core · all containers", symbol: "cpu", color: .mint)
metricCard("Memory", value: current.map { ContainerService.bytes($0.memory) } ?? "—", subtitle: "total working usage", symbol: "memorychip", color: .purple)
metricCard("Network", value: current.map { ContainerService.bytes($0.rx + $0.tx) + "/s" } ?? "—", subtitle: "combined receive + transmit", symbol: "arrow.up.arrow.down", color: .blue)
}
LazyVGrid(columns: columns, spacing: 16) {
graphCard("CPU usage", subtitle: "Percentage of one core", color: .mint, keyPath: \.cpu, divisor: 1, unit: "%")
graphCard("Memory", subtitle: "Aggregate container memory · MiB", color: .purple, keyPath: \.memory, divisor: 1_048_576, unit: "")
}
networkCard
HStack { Text("Container activity").font(.title3.bold()); Spacer(); Button("Manage containers") { model.kind = .containers; model.changeKind() } }
VStack(spacing: 0) {
ForEach(model.containerResources) { r in
HStack(spacing: 12) {
Image(systemName: "shippingbox").foregroundStyle(.orange).frame(width: 28, height: 28).background(Color.orange.opacity(0.1), in: RoundedRectangle(cornerRadius: 8))
VStack(alignment: .leading, spacing: 3) { Text(r.name).font(.system(.body, design: .monospaced).weight(.medium)); Text(r.image).font(.caption).foregroundStyle(.secondary).lineLimit(1) }
Spacer(); Label(r.state, systemImage: "circle.fill").font(.caption).foregroundStyle(r.state == "running" ? .mint : .secondary).frame(width: 90, alignment: .leading)
Text(r.cpu).monospacedDigit().frame(width: 65, alignment: .trailing)
Text(r.memory).monospacedDigit().frame(width: 85, alignment: .trailing)
}.padding(12)
if r.id != model.containerResources.last?.id { Divider().padding(.leading, 52) }
}
if model.containerResources.isEmpty { Text("No application containers found. Start a container to see live metrics.").foregroundStyle(.secondary).padding(24) }
}.background(.background, in: RoundedRectangle(cornerRadius: 12)).overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(.primary.opacity(0.08)))
if !model.lastAction.isEmpty { Label(model.lastAction, systemImage: "checkmark.circle").foregroundStyle(.secondary).font(.caption) }
Text("Charts retain up to six minutes of in-app history. Network rates use differences between cumulative counters; unavailable metrics are shown as —. CPU can exceed 100% across multiple cores.").font(.caption).foregroundStyle(.secondary)
}.padding(24)
}.background(Color(nsColor: .underPageBackgroundColor))
}
private func metricCard(_ title: String, value: String, subtitle: String, symbol: String, color: Color) -> some View {
VStack(alignment: .leading, spacing: 12) { HStack { Label(title, systemImage: symbol).font(.subheadline.weight(.medium)).foregroundStyle(.secondary); Spacer() }; Text(value).font(.system(size: 25, weight: .semibold, design: .rounded)).monospacedDigit().lineLimit(1).minimumScaleFactor(0.6).foregroundStyle(color); Text(subtitle).font(.caption).foregroundStyle(.secondary).lineLimit(1) }.padding(16).frame(maxWidth: .infinity, alignment: .leading).background(.background, in: RoundedRectangle(cornerRadius: 12)).overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(color.opacity(0.18)))
}
private func graphCard(_ title: String, subtitle: String, color: Color, keyPath: KeyPath<TelemetryPoint, Double>, divisor: Double, unit: String) -> some View {
VStack(alignment: .leading, spacing: 14) {
HStack { VStack(alignment: .leading, spacing: 4) { Text(title).font(.headline); Text(subtitle).font(.caption).foregroundStyle(.secondary) }; Spacer(); Text(current.map { String(format: "%.1f", $0[keyPath: keyPath] / divisor) + unit } ?? "—").font(.title3.bold()).monospacedDigit().foregroundStyle(color) }
Chart(model.history) { p in
AreaMark(x: .value("Time", p.date), y: .value(title, p[keyPath: keyPath] / divisor)).foregroundStyle(LinearGradient(colors: [color.opacity(0.26), color.opacity(0.01)], startPoint: .top, endPoint: .bottom)).interpolationMethod(.monotone)
LineMark(x: .value("Time", p.date), y: .value(title, p[keyPath: keyPath] / divisor)).foregroundStyle(color).lineStyle(StrokeStyle(lineWidth: 2)).interpolationMethod(.monotone)
}.chartYScale(domain: .automatic(includesZero: true)).chartXAxis { AxisMarks(values: .automatic(desiredCount: 4)) { _ in AxisGridLine(); AxisValueLabel(format: .dateTime.hour().minute().second()) } }.chartYAxis { AxisMarks(position: .leading) }.frame(height: 160).overlay { if model.history.isEmpty { Text("Waiting for supported container metrics…").font(.caption).foregroundStyle(.secondary) } }
}.padding(18).background(.background, in: RoundedRectangle(cornerRadius: 12)).overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(.primary.opacity(0.08)))
}
private var networkCard: some View {
VStack(alignment: .leading, spacing: 14) {
HStack { VStack(alignment: .leading, spacing: 4) { Text("Network throughput").font(.headline); Text("Receive and transmit · KiB/s").font(.caption).foregroundStyle(.secondary) }; Spacer(); Label("Receive", systemImage: "arrow.down").foregroundStyle(.blue); Label("Transmit", systemImage: "arrow.up").foregroundStyle(.orange) }.font(.caption)
Chart(model.history) { p in
LineMark(x: .value("Time", p.date), y: .value("KiB/s", p.rx / 1024), series: .value("Direction", "Receive")).foregroundStyle(.blue).interpolationMethod(.monotone)
LineMark(x: .value("Time", p.date), y: .value("KiB/s", p.tx / 1024), series: .value("Direction", "Transmit")).foregroundStyle(.orange).interpolationMethod(.monotone)
}.chartYScale(domain: .automatic(includesZero: true)).chartXAxis { AxisMarks(values: .automatic(desiredCount: 5)) { _ in AxisGridLine(); AxisValueLabel(format: .dateTime.hour().minute().second()) } }.chartYAxis { AxisMarks(position: .leading) }.frame(height: 145).overlay { if model.history.isEmpty { Text("Waiting for supported container metrics…").font(.caption).foregroundStyle(.secondary) } }
}.padding(18).background(.background, in: RoundedRectangle(cornerRadius: 12)).overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(.primary.opacity(0.08)))
}
}
+41
View File
@@ -0,0 +1,41 @@
name: laravel
services:
web:
build:
context: ./docker/php
args:
uid: ${UID:-1000}
gid: ${GID:-1000}
depends_on:
- db
volumes:
- ./:/home/devuser/app
- ./storage/tmp/composer:/home/devuser/.composer/cache
- ./storage/tmp/npm:/home/devuser/.npm
ports:
- ${WORKSPACE_PORT:-8080}:8080
restart: always
db:
image: mysql:5.7
environment:
- MYSQL_ROOT_PASSWORD=root_secret
- MYSQL_DATABASE=zeus
- MYSQL_USER=zeus_user
- MYSQL_PASSWORD=secret
volumes:
- ./storage/tmp/db:/var/lib/mysql
restart: always
adminer:
image: adminer
restart: always
ports:
- 9000:8080
nginx:
build: ./docker/nginx
ports:
- ${NGINX_PORT:-80}:80
volumes:
- ./:/home/devuser/app
depends_on:
- web
restart: always
@@ -0,0 +1,99 @@
import XCTest
@testable import ACDryCore
final class ACDryCoreTests: XCTestCase {
func testCurrentAndLegacySchemas() throws {
let rows = try ContainerService.parse(ContainerService.demoContainers, kind: .containers)
XCTAssertEqual(rows.count, 2); XCTAssertEqual(rows[1].id, "demo-web-1"); XCTAssertEqual(rows[1].state, "running"); XCTAssertEqual(rows[1].project, "demo")
XCTAssertEqual(try ContainerService.parse("[{\"reference\":\"alpine:latest\"}]", kind: .images).first?.id, "alpine:latest")
XCTAssertEqual(try ContainerService.parse("[{\"configuration\":{\"name\":\"data\"}}]", kind: .volumes).first?.id, "data")
XCTAssertEqual(try ContainerService.parse("[{\"id\":\"default\"}]", kind: .networks).first?.id, "default")
for invalid in ["oops", "{}", "[{}]"] { XCTAssertThrowsError(try ContainerService.parse(invalid, kind: .containers)) }
}
func testComposeHintIsExplicitAndExact() throws {
let input = "[{\"configuration\":{\"id\":\"my-app-web-api-1\"}},{\"configuration\":{\"id\":\"other-web-1\"}}]"
XCTAssertEqual(try ContainerService.parse(input, kind: .containers).first?.project, "")
let rows = try ContainerService.parse(input, kind: .projects, projectHint: "my-app")
XCTAssertEqual(rows[0].project, "my-app"); XCTAssertEqual(rows[0].service, "web-api"); XCTAssertEqual(rows[1].project, "")
}
func testNativeArguments() {
XCTAssertEqual(ResourceKind.containers.listArguments, ["list", "--all", "--format", "json"])
XCTAssertEqual(ContainerService.detailArguments(kind: .containers, id: "web", mode: "Logs"), ["logs", "-n", "200", "web"])
XCTAssertEqual(ContainerService.detailArguments(kind: .networks, id: "default", mode: "Inspect"), ["network", "inspect", "default"])
XCTAssertEqual(ContainerService.actionArguments(kind: .projects, action: "prune", id: ""), ["prune"])
XCTAssertEqual(ContainerService.actionArguments(kind: .images, action: "delete", id: "alpine"), ["image", "delete", "alpine"])
}
func testDemoAndCPUUnits() async throws {
let backend = ContainerService(demo: true)
let resources = try await backend.withStats(backend.list(.containers))
let measured = resources.first(where: { $0.id == "demo-web-1" })!
XCTAssertEqual(measured.cpu, String(format: "%.2f%%", measured.cpuFraction! * 100))
XCTAssertGreaterThan(measured.cpuFraction!, 0)
do { _ = try await backend.action(kind: .containers, action: "restart", resource: resources.first); XCTFail("Demo must not claim mutation success") } catch { XCTAssertTrue(error.localizedDescription.contains("read-only")) }
}
func testProcessCaptureFailureAndTimeout() throws {
XCTAssertEqual(try ContainerService(binary: "/bin/echo").execute(["hello", "$(not-a-shell-command)"]).trimmingCharacters(in: .whitespacesAndNewlines), "hello $(not-a-shell-command)")
XCTAssertThrowsError(try ContainerService(binary: "/usr/bin/false").execute([]))
XCTAssertThrowsError(try ContainerService(binary: "/nonexistent/container").execute([]))
let start = Date(); XCTAssertThrowsError(try ContainerService(binary: "/bin/sleep").execute(["10"], timeout: 0.1)); XCTAssertLessThan(Date().timeIntervalSince(start), 4)
}
func testNetworkRatesAndCounterResets() async throws {
var rows = try await ContainerService(demo: true).withStats(ContainerService(demo: true).list(.containers))
var tracker = NetworkRateTracker()
let date = Date(timeIntervalSince1970: 0)
XCTAssertEqual(tracker.sample(rows, at: date).rx, 0)
let n = rows.firstIndex(where: { $0.id == "demo-web-1" })!
rows[n].rxBytes! += 3000; rows[n].txBytes! += 1500
let rates = tracker.sample(rows, at: date.addingTimeInterval(3))
XCTAssertEqual(rates.rx, 1000); XCTAssertEqual(rates.tx, 500)
rows[n].rxBytes = 0; rows[n].txBytes = 0
XCTAssertEqual(tracker.sample(rows, at: date.addingTimeInterval(6)).rx, 0)
_ = tracker.sample([], at: date.addingTimeInterval(9))
rows[n].rxBytes = 1_000_000
XCTAssertEqual(tracker.sample(rows, at: date.addingTimeInterval(12)).rx, 0)
}
func testRestartStopsBeforeStartingAndStopsOnFailure() async throws {
let dir = FileManager.default.temporaryDirectory.appendingPathComponent("acdry-tests-" + UUID().uuidString)
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: dir) }
let executable = dir.appendingPathComponent("container"), log = dir.appendingPathComponent("calls"), failure = dir.appendingPathComponent("fail-stop")
let script = "#!/bin/sh\necho \"$1\" >> \"\(log.path)\"\nif [ \"$1\" = stop ] && [ -f \"\(failure.path)\" ]; then exit 1; fi\n"
try script.write(to: executable, atomically: true, encoding: .utf8)
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: executable.path)
let backend = ContainerService(binary: executable.path)
let r = try ContainerService.parse(ContainerService.demoContainers, kind: .containers).first(where: { $0.state == "running" })!
_ = try await backend.action(kind: .containers, action: "restart", resource: r)
XCTAssertEqual(try String(contentsOf: log, encoding: .utf8), "stop\nstart\n")
try "".write(to: log, atomically: true, encoding: .utf8); try "".write(to: failure, atomically: true, encoding: .utf8)
do { _ = try await backend.action(kind: .containers, action: "restart", resource: r); XCTFail("Should fail") } catch { }
XCTAssertEqual(try String(contentsOf: log, encoding: .utf8), "stop\n")
}
func testStateVerificationAndReadOnlyDemo() async throws {
let backend = ContainerService(demo: true)
let resource = try await backend.waitForState(id: "demo-web-1", expected: "running", timeout: 1)
XCTAssertEqual(resource.state, "running")
do { _ = try await backend.waitForState(id: "demo-web-1", expected: "stopped", timeout: 0.01); XCTFail("Should time out") } catch { XCTAssertTrue(error.localizedDescription.contains("did not reach stopped")) }
do { _ = try await backend.run(["stop", "demo-web-1"]); XCTFail("Demo must be read-only") } catch { XCTAssertTrue(error.localizedDescription.contains("read-only")) }
}
func testVolumeMetadataAndMountReferences() throws {
let volumes = try ContainerService.parse("""
[{"configuration":{"name":"app-data","driver":"local","format":"ext4","source":"/volumes/app-data.img","sizeInBytes":67108864,"creationDate":"2026-11-08T12:00:00Z"}}]
""", kind: .volumes)
XCTAssertEqual(volumes[0].driver, "local"); XCTAssertEqual(volumes[0].format, "ext4"); XCTAssertNotEqual(volumes[0].capacity, "—")
let containers = try ContainerService.parse("""
[{"configuration":{"id":"app","mounts":[{"source":"/volumes/app-data.img","destination":"/data","type":{"volume":{"name":"app-data"}}},{"source":"/Users/example/project","destination":"/workspace","type":{"virtiofs":{}}},{"source":"","destination":"/tmp","type":{"tmpfs":{}}}]},"status":{"state":"stopped"}}]
""", kind: .containers)
let combined = VolumeInventory.combine(volumes: volumes, containers: containers)
XCTAssertEqual(combined.count, 2)
XCTAssertEqual(combined[0].usedBy, "app:/data"); XCTAssertFalse(combined[0].readOnlyStorage)
XCTAssertEqual(combined[1].storageKind, "Bind mount"); XCTAssertTrue(combined[1].readOnlyStorage); XCTAssertEqual(combined[1].usedBy, "app:/workspace")
XCTAssertEqual(VolumeInventory.combine(volumes: volumes, containers: containers, includeHostMounts: false).count, 1)
let legacy = try ContainerService.parse("[{\"name\":\"legacy\",\"driver\":\"local\",\"format\":\"ext4\",\"source\":\"/legacy\",\"createdAt\":\"2025-01-01\"}]", kind: .volumes)
XCTAssertEqual(legacy[0].created, "2025-01-01"); XCTAssertEqual(legacy[0].source, "/legacy")
}
func testConcurrentPipeDrainAndOutputLimit() throws {
// A subprocess that fills both pipes would deadlock a sequential reader.
let out = try ContainerService(binary: "/bin/sh").execute(["-c", "i=0; while [ $i -lt 5000 ]; do echo stdout-line; echo stderr-line >&2; i=$((i+1)); done"])
XCTAssertEqual(out.components(separatedBy: "stdout-line").count - 1, 5000)
}
}
+2 -2
View File
@@ -1,11 +1,11 @@
#!/usr/bin/env python3
"""Run against a built CLI: python3 Tests/cli_regression.py /absolute/path/accompose.
"""Run against a built CLI: python3 Tests/cli_regression.py /absolute/path/container-compose.
Uses a fake container executable; never touches the real runtime.
"""
import json, os, pathlib, signal, subprocess, sys, tempfile, time
binary = str(pathlib.Path(sys.argv[1]).resolve())
with tempfile.TemporaryDirectory(prefix='accompose-regression-', dir='/private/tmp') as tmp:
with tempfile.TemporaryDirectory(prefix='container-compose-regression-', dir=os.environ.get('CONTAINER_COMPOSE_TEST_TMPDIR', '/Volumes/ChatGPT' if pathlib.Path('/Volumes/ChatGPT').is_dir() else tempfile.gettempdir())) as tmp:
root = pathlib.Path(tmp)
fake = root / 'container'
fake.write_text('#!' + sys.executable + '\n' + r'''
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env python3
"""Unsupported YAML must fail before any runtime call."""
import os, pathlib, subprocess, sys, tempfile
binary = str(pathlib.Path(sys.argv[1]).resolve())
with tempfile.TemporaryDirectory(prefix='container-compose-compat-', dir=os.environ.get('CONTAINER_COMPOSE_TEST_TMPDIR', '/Volumes/ChatGPT' if pathlib.Path('/Volumes/ChatGPT').is_dir() else tempfile.gettempdir())) as d:
root = pathlib.Path(d)
runtime = root/'container'
runtime.write_text('#!/bin/sh\necho called >> "$CALLS"\nexit 99\n')
runtime.chmod(0o755)
calls=root/'calls'; source=root/'compose.yaml'
env=dict(os.environ, PATH=str(root)+':'+os.environ['PATH'], CALLS=str(calls))
fixtures = [
'secrets: {}\nservices:\n web:\n image: nginx\n',
'volumes:\n data:\n external: true\nservices:\n web:\n image: nginx\n',
'networks:\n lan:\n driver: overlay\nservices:\n web:\n image: nginx\n',
'services:\n web:\n image: nginx\n ports: [{target: 80, published: 8080}]\n',
'services:\n web:\n image: nginx\n volumes: [{type: bind, source: ., target: /app}]\n',
'services:\n web:\n image: nginx\n networks:\n lan:\n aliases: [web]\n',
'services:\n web:\n image: nginx\n depends_on:\n db:\n condition: service_healthy\n db:\n image: postgres\n',
'services:\n web:\n build:\n context: .\n target: production\n',
'services:\n web:\n image: nginx\n profiles: [dev]\n',
'services:\n web:\n image: nginx\n deploy: {replicas: 3}\n',
]
for fixture in fixtures:
source.write_text(fixture)
result=subprocess.run([binary,'-f',str(source),'up','-d'],env=env,capture_output=True,timeout=5)
assert result.returncode != 0, result.stdout
assert not calls.exists(), 'Unexpected runtime mutation'
source.write_text('services:\n web:\n image: nginx\n')
for command in [['down','--bogus'],['logs','--follow'],['stop','unknown']]:
result=subprocess.run([binary,'-f',str(source)]+command,env=env,capture_output=True,timeout=5)
assert result.returncode != 0
assert not calls.exists()
result=subprocess.run([binary,'monitor','--demo','start','demo-web-1'],env=env,capture_output=True,timeout=5)
assert result.returncode != 0 and not calls.exists()
print('PASS: 14 compatibility and read-only demo checks')
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""Usage: python3 Tests/laravel_regression.py /absolute/path/container-compose"""
import json, os, pathlib, subprocess, sys, tempfile
binary=str(pathlib.Path(sys.argv[1]).resolve())
with tempfile.TemporaryDirectory(prefix='container-compose-laravel-',dir=os.environ.get('CONTAINER_COMPOSE_TEST_TMPDIR', '/Volumes/ChatGPT' if pathlib.Path('/Volumes/ChatGPT').is_dir() else tempfile.gettempdir())) as tmp:
root=pathlib.Path(tmp); calls=root/'calls'
runtime=root/'container'
runtime.write_text('#!'+sys.executable+'\nimport os,json,sys\nwith open(os.environ["CALLS"],"a") as f: f.write(json.dumps(sys.argv[1:])+"\\n")\n')
runtime.chmod(0o755)
env=dict(os.environ,PATH=str(root)+':'+os.environ.get('PATH',''),CALLS=str(calls),UID='1234',GID='2345')
path=root/'compose.yaml'
def run(command='config'):
return subprocess.run([binary,'-f',str(path),command],env=env,text=True,stdout=subprocess.PIPE,stderr=subprocess.STDOUT,timeout=10)
cases=[('', 'Compose file is empty'),(' # comment\n','mapping at its root'),('name: wrong\n','Missing top-level services'),('services: []\n','nonempty mapping'),('services: {}\n','nonempty mapping'),('services: [\n','Invalid YAML')]
for content,expected in cases:
path.write_text(content);p=run();assert p.returncode!=0 and expected in p.stdout and path.name in p.stdout,p.stdout
path.write_text((pathlib.Path(__file__).parent/'Fixtures/laravel.yaml').read_text())
p=run();assert p.returncode==0 and 'Validated 4 services' in p.stdout,p.stdout
p=run('build');assert p.returncode==0,p.stdout
records=[json.loads(x) for x in calls.read_text().splitlines()]
assert len(records)==2,records
assert records[0]==['build','-t','laravel-nginx:latest',str((root/'docker/nginx').resolve())],records
assert records[1]==['build','-t','laravel-web:latest','--build-arg','gid=2345','--build-arg','uid=1234',str((root/'docker/php').resolve())],records
calls.write_text('')
path.write_text('services:\n web:\n build:\n context: .\n args: ["UID", "LITERAL=hello world"]\n')
p=run('build');assert p.returncode==0,p.stdout
records=[json.loads(x) for x in calls.read_text().splitlines()]
assert ['--build-arg','UID=1234','--build-arg','LITERAL=hello world']==records[0][3:7],records
print('PASS: 9 Laravel and file diagnostic cases')
+33
View File
@@ -0,0 +1,33 @@
# Container Compose 0.2.0 validation — 9 November 2026
Environment: Apple silicon, Xcode 27.2 (27B52), Apple container CLI 1.7.1. App deployment target macOS 26.0. Source checkout: ~/Developer/Applications/AppleContainerCompose. Task builds, logs and original-source backup: /Volumes/ChatGPT/container-compose-merge-20261109.
- Xcode ContainerCompose scheme Debug and Release: succeeded, including its container-compose target dependency and embedded executable. Code signing disabled for build verification.
- CLI foreground/detached suite: 27 cases passed. Both detach flags, all verbosity levels/positions, live log arrival, parent/group Ctrl+C, interrupted startup, follower failure and unknown up options are covered with a fake runtime.
- Laravel/file-diagnostic suite: 9 cases passed, including build-arg forwarding.
- Compatibility/read-only demo suite: 14 checks passed. Unsupported root/resource/build options, long ports/mounts, aliases, health gating, profiles, deployment, unknown services/options and demo mutations fail before runtime calls.
- Recovered acdry core XCTest suite: 10 tests passed, zero failures, against the merged core (including disk-stat addition). Schema parsing, explicit project inference, native commands, error/timeout handling, counter resets and volume inventory are covered.
- Embedded Release CLI --version and two NDJSON demo-watch samples: passed. Demo source is explicitly labelled.
- Read-only live container inventory and statistics: passed. This host had only a stopped buildkit container, so stats returned an empty array. This does not validate live CPU/network/disk behavior under workload.
- Native UI smoke check: opened the merged app, observed Compose controls and switched to Monitor & Resources; the dashboard listed the local stopped buildkit container and showed missing metrics as unavailable. This was a limited UI smoke check, not exhaustive interaction testing.
- Supplied ACDry-Swift-1.1.0.zip: all 14 source-package files matched the recovered local 1.1.0 archive byte-for-byte before modification.
No existing user container was started, stopped or deleted for validation. Real multi-service networking, builds, service DNS and interactive terminal sessions were not revalidated in this task. CLI lifecycle validation used a fake executable. Earlier project validation files describe prior versions and are historical only.
## Reproduce
```sh
BUILD_DIR="$(mktemp -d /Volumes/ChatGPT/container-compose-check.XXXXXX)"
xcodebuild -project ContainerCompose.xcodeproj -scheme ContainerCompose \
-configuration Release -derivedDataPath "$BUILD_DIR/xcode" \
CODE_SIGNING_ALLOWED=NO build
BIN="$BUILD_DIR/xcode/Build/Products/Release/container-compose"
python3 Tests/cli_regression.py "$BIN"
python3 Tests/laravel_regression.py "$BIN"
python3 Tests/compatibility_regression.py "$BIN"
swift test --scratch-path "$BUILD_DIR/tests" --cache-path "$BUILD_DIR/cache" \
--config-path "$BUILD_DIR/config" --security-path "$BUILD_DIR/security"
"$BIN" monitor --demo watch --json --count 2
```
Use an operating-system temporary directory if /Volumes/ChatGPT is unavailable. Package.swift is a monitor-test package; build the app and full CLI using the Xcode project. See README.md for supported features and explicit limitations. Binaries are not signed/notarized for distribution; the ZIP delivers complete source and package lockfile. User-specific examples, Git data, IDE state, build output and old validation logs are excluded from the downloadable ZIP.